diff options
Diffstat (limited to 'libs')
619 files changed, 20525 insertions, 10424 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 00e13c94ea90..ee8ec1dd1008 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -239,6 +239,11 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) { + if (secondary == null) { + wct.clearAdjacentTaskFragments(primary); + return; + } + WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null; final boolean finishSecondaryWithPrimary = splitRule != null && SplitContainer.shouldFinishSecondaryWithPrimary(splitRule); @@ -310,16 +315,12 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { OP_TYPE_SET_ANIMATION_PARAMS) .setAnimationParams(animationParams) .build(); - wct.setTaskFragmentOperation(fragmentToken, operation); + wct.addTaskFragmentOperation(fragmentToken, operation); } void deleteTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken) { - if (!mFragmentInfos.containsKey(fragmentToken)) { - throw new IllegalArgumentException( - "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken); - } - wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken()); + wct.deleteTaskFragment(fragmentToken); } void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 8b3a471ea306..b13c6724ed0e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -20,19 +20,19 @@ import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; +import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE; +import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; -import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; -import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; @@ -67,6 +67,7 @@ import android.util.SparseArray; import android.view.WindowMetrics; import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentOperation; import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; @@ -355,7 +356,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen container.setInfo(wct, taskFragmentInfo); if (container.isFinished()) { - mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); + mTransactionManager.getCurrentTransactionRecord() + .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else { // Update with the latest Task configuration. @@ -391,22 +393,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Do not finish the dependents if the last activity is reparented to PiP. // Instead, the original split should be cleanup, and the dependent may be // expanded to fullscreen. - mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); + mTransactionManager.getCurrentTransactionRecord() + .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); cleanupForEnterPip(wct, container); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else if (taskFragmentInfo.isTaskClearedForReuse()) { // Do not finish the dependents if this TaskFragment was cleared due to // launching activity in the Task. - mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); + mTransactionManager.getCurrentTransactionRecord() + .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else if (taskFragmentInfo.isClearedForReorderActivityToFront()) { // Do not finish the dependents if this TaskFragment was cleared to reorder // the launching Activity to front of the Task. + mTransactionManager.getCurrentTransactionRecord() + .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else if (!container.isWaitingActivityAppear()) { // Do not finish the container before the expected activity appear until // timeout. - mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); + mTransactionManager.getCurrentTransactionRecord() + .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */); } } else if (wasInPip && isInPip) { @@ -586,11 +593,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, - int opType, @NonNull Throwable exception) { + @TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception) { Log.e(TAG, "onTaskFragmentError=" + exception.getMessage()); switch (opType) { - case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: - case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { + case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: + case OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { final TaskFragmentContainer container; if (taskFragmentInfo != null) { container = getContainer(taskFragmentInfo.getFragmentToken()); @@ -605,7 +612,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen container.setInfo(wct, taskFragmentInfo); container.clearPendingAppearedActivities(); if (container.isEmpty()) { - mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); + mTransactionManager.getCurrentTransactionRecord() + .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } break; @@ -1022,7 +1030,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { - mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); + mTransactionManager.getCurrentTransactionRecord() + .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } @@ -1614,7 +1623,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // (not resumed yet). if (isOnCreated || primaryActivity.isResumed()) { // Only set trigger type if the launch happens in foreground. - mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_OPEN); + mTransactionManager.getCurrentTransactionRecord() + .setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); return null; } final ActivityOptions options = ActivityOptions.makeBasic(); @@ -1642,7 +1652,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } - mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); + mTransactionManager.getCurrentTransactionRecord() + .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(), false /* shouldFinishDependent */); return true; @@ -1946,7 +1957,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen synchronized (mLock) { final TransactionRecord transactionRecord = mTransactionManager .startNewTransaction(); - transactionRecord.setOriginType(TRANSIT_OPEN); + transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); SplitController.this.onActivityCreated(transactionRecord.getTransaction(), activity); // The WCT should be applied and merged to the activity launch transition. @@ -2036,7 +2047,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen synchronized (mLock) { final TransactionRecord transactionRecord = mTransactionManager .startNewTransaction(); - transactionRecord.setOriginType(TRANSIT_OPEN); + transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); final WindowContainerTransaction wct = transactionRecord.getTransaction(); final TaskFragmentContainer launchedInTaskFragment; if (launchingActivity != null) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 668a7d5aa9b6..85a00dfc010c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -30,6 +30,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; +import android.util.DisplayMetrics; import android.util.LayoutDirection; import android.util.Pair; import android.util.Size; @@ -972,6 +973,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) { final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds(); // TODO(b/190433398): Supply correct insets. - return new WindowMetrics(taskBounds, WindowInsets.CONSUMED); + final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java index 0071fea41aa8..396956e56bb5 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java @@ -16,12 +16,12 @@ package androidx.window.extensions.embedding; -import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_NONE; +import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE; +import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_NONE; import android.os.IBinder; -import android.view.WindowManager.TransitionType; import android.window.TaskFragmentOrganizer; +import android.window.TaskFragmentOrganizer.TaskFragmentTransitionType; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -122,8 +122,8 @@ class TransactionManager { * @see #setOriginType(int) * @see #getTransactionTransitionType() */ - @TransitionType - private int mOriginType = TRANSIT_NONE; + @TaskFragmentTransitionType + private int mOriginType = TASK_FRAGMENT_TRANSIT_NONE; TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) { mTaskFragmentTransactionToken = taskFragmentTransactionToken; @@ -136,12 +136,12 @@ class TransactionManager { } /** - * Sets the {@link TransitionType} that triggers this transaction. If there are multiple - * calls, only the first call will be respected as the "origin" type. + * Sets the {@link TaskFragmentTransitionType} that triggers this transaction. If there are + * multiple calls, only the first call will be respected as the "origin" type. */ - void setOriginType(@TransitionType int type) { + void setOriginType(@TaskFragmentTransitionType int type) { ensureCurrentTransaction(); - if (mOriginType != TRANSIT_NONE) { + if (mOriginType != TASK_FRAGMENT_TRANSIT_NONE) { // Skip if the origin type has already been set. return; } @@ -188,14 +188,16 @@ class TransactionManager { } /** - * Gets the {@link TransitionType} that we will request transition with for the + * Gets the {@link TaskFragmentTransitionType} that we will request transition with for the * current {@link WindowContainerTransaction}. */ @VisibleForTesting - @TransitionType + @TaskFragmentTransitionType int getTransactionTransitionType() { - // Use TRANSIT_CHANGE as default if there is not opening/closing window. - return mOriginType != TRANSIT_NONE ? mOriginType : TRANSIT_CHANGE; + // Use TASK_FRAGMENT_TRANSIT_CHANGE as default if there is not opening/closing window. + return mOriginType != TASK_FRAGMENT_TRANSIT_NONE + ? mOriginType + : TASK_FRAGMENT_TRANSIT_CHANGE; } } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 0bf0bc85b511..a26311efc23e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -20,13 +20,13 @@ import static android.app.ActivityManager.START_CANCELED; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; +import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; -import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY; import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY; @@ -1139,7 +1139,7 @@ public class SplitControllerTest { final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); final IBinder errorToken = new Binder(); final TaskFragmentInfo info = mock(TaskFragmentInfo.class); - final int opType = HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; + final int opType = OP_TYPE_CREATE_TASK_FRAGMENT; final Exception exception = new SecurityException("test"); final Bundle errorBundle = TaskFragmentOrganizer.putErrorInfoInBundle(exception, info, opType); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index ff1256b47429..07d01589be5a 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -66,8 +66,10 @@ import android.graphics.Color; import android.graphics.Rect; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.util.DisplayMetrics; import android.util.Pair; import android.util.Size; +import android.view.WindowMetrics; import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOperation; @@ -101,7 +103,6 @@ import java.util.ArrayList; @RunWith(AndroidJUnit4.class) public class SplitPresenterTest { - @Mock private Activity mActivity; @Mock private Resources mActivityResources; @@ -193,7 +194,7 @@ public class SplitPresenterTest { OP_TYPE_SET_ANIMATION_PARAMS) .setAnimationParams(animationParams) .build(); - verify(mTransaction).setTaskFragmentOperation(container.getTaskFragmentToken(), + verify(mTransaction).addTaskFragmentOperation(container.getTaskFragmentToken(), expectedOperation); assertTrue(container.areLastRequestedAnimationParamsEqual(animationParams)); @@ -202,7 +203,7 @@ public class SplitPresenterTest { mPresenter.updateAnimationParams(mTransaction, container.getTaskFragmentToken(), animationParams); - verify(mTransaction, never()).setTaskFragmentOperation(any(), any()); + verify(mTransaction, never()).addTaskFragmentOperation(any(), any()); } @Test @@ -571,6 +572,21 @@ public class SplitPresenterTest { splitPairRule, null /* minDimensionsPair */)); } + @Test + public void testGetTaskWindowMetrics() { + final Configuration taskConfig = new Configuration(); + taskConfig.windowConfiguration.setBounds(TASK_BOUNDS); + taskConfig.densityDpi = 123; + final TaskContainer.TaskProperties taskProperties = new TaskContainer.TaskProperties( + DEFAULT_DISPLAY, taskConfig); + doReturn(taskProperties).when(mPresenter).getTaskProperties(mActivity); + + final WindowMetrics windowMetrics = mPresenter.getTaskWindowMetrics(mActivity); + assertEquals(TASK_BOUNDS, windowMetrics.getBounds()); + assertEquals(123 * DisplayMetrics.DENSITY_DEFAULT_SCALE, + windowMetrics.getDensity(), 0f); + } + private Activity createMockActivity() { final Activity activity = mock(Activity.class); final Configuration activityConfig = new Configuration(); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java index 62006bd51399..459b6d2c31f9 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java @@ -16,9 +16,9 @@ package androidx.window.extensions.embedding; -import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE; +import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE; +import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; @@ -86,27 +86,29 @@ public class TransactionManagerTest { @Test public void testSetTransactionOriginType() { - // Return TRANSIT_CHANGE if there is no trigger type set. + // Return TASK_FRAGMENT_TRANSIT_CHANGE if there is no trigger type set. TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); - assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType()); + assertEquals(TASK_FRAGMENT_TRANSIT_CHANGE, + transactionRecord.getTransactionTransitionType()); // Return the first set type. mTransactionManager.getCurrentTransactionRecord().abort(); transactionRecord = mTransactionManager.startNewTransaction(); - transactionRecord.setOriginType(TRANSIT_OPEN); + transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); - assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType()); + assertEquals(TASK_FRAGMENT_TRANSIT_OPEN, transactionRecord.getTransactionTransitionType()); - transactionRecord.setOriginType(TRANSIT_CLOSE); + transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); - assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType()); + assertEquals(TASK_FRAGMENT_TRANSIT_OPEN, transactionRecord.getTransactionTransitionType()); // Reset when #startNewTransaction(). transactionRecord.abort(); transactionRecord = mTransactionManager.startNewTransaction(); - assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType()); + assertEquals(TASK_FRAGMENT_TRANSIT_CHANGE, + transactionRecord.getTransactionTransitionType()); } @Test diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index 8881be7326a9..36d3313a9f3b 100644 --- a/libs/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml @@ -21,5 +21,6 @@ <uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" /> + <uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" /> <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> </manifest> diff --git a/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml index 7475abac4695..7475abac4695 100644 --- a/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml +++ b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml index ce8640df0093..67467bbc72ae 100644 --- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml +++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml @@ -15,5 +15,5 @@ ~ limitations under the License. --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@color/tv_pip_menu_icon_unfocused" /> + <item android:color="@color/tv_window_menu_icon_unfocused" /> </selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml index 4f5e63dac5c0..4182bfeefa1b 100644 --- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml +++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2021 The Android Open Source Project + ~ Copyright (C) 2022 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. @@ -16,6 +16,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true" - android:color="@color/tv_pip_menu_icon_bg_focused" /> - <item android:color="@color/tv_pip_menu_icon_bg_unfocused" /> + android:color="@color/tv_window_menu_close_icon_bg_focused" /> + <item android:color="@color/tv_window_menu_close_icon_bg_unfocused" /> </selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml index 275870450493..45205d2a7138 100644 --- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml +++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2021 The Android Open Source Project + ~ Copyright (C) 2022 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. @@ -16,8 +16,8 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true" - android:color="@color/tv_pip_menu_icon_focused" /> + android:color="@color/tv_window_menu_icon_focused" /> <item android:state_enabled="false" - android:color="@color/tv_pip_menu_icon_disabled" /> - <item android:color="@color/tv_pip_menu_icon_unfocused" /> + android:color="@color/tv_window_menu_icon_disabled" /> + <item android:color="@color/tv_window_menu_icon_unfocused" /> </selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml index 6cbf66f00df7..1bd26e1d6583 100644 --- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml +++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml @@ -16,6 +16,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true" - android:color="@color/tv_pip_menu_close_icon_bg_focused" /> - <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" /> + android:color="@color/tv_window_menu_icon_bg_focused" /> + <item android:color="@color/tv_window_menu_icon_bg_unfocused" /> </selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml deleted file mode 100644 index 1938f4562e97..000000000000 --- a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2020 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. ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <corners android:radius="@dimen/pip_menu_button_radius" /> - <solid android:color="@color/tv_pip_menu_icon_bg" /> -</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml index 846fdb3e8a58..7085a2c72c86 100644 --- a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml +++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> <selector xmlns:android="http://schemas.android.com/apk/res/android" - android:exitFadeDuration="@integer/pip_menu_fade_animation_duration"> + android:exitFadeDuration="@integer/tv_window_menu_fade_animation_duration"> <item android:state_activated="true"> <shape android:shape="rectangle"> <corners android:radius="@dimen/pip_menu_border_corner_radius" /> diff --git a/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_focus.xml b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_focus.xml new file mode 100644 index 000000000000..a348b148afb4 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_focus.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2022 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="@dimen/tv_window_menu_icon_size" + android:height="@dimen/tv_window_menu_icon_size" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M17,4h3c1.1,0 2,0.9 2,2v2h-2L20,6h-3L17,4zM4,8L4,6h3L7,4L4,4c-1.1,0 -2,0.9 -2,2v2h2zM20,16v2h-3v2h3c1.1,0 2,-0.9 2,-2v-2h-2zM7,18L4,18v-2L2,16v2c0,1.1 0.9,2 2,2h3v-2zM18,8L6,8v8h12L18,8z"/> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_swap.xml b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_swap.xml new file mode 100644 index 000000000000..c5d54c5fa4f2 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_swap.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2022 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="@dimen/tv_window_menu_icon_size" + android:height="@dimen/tv_window_menu_icon_size" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFF" + android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"/> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml new file mode 100644 index 000000000000..2dba37daf059 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="@dimen/tv_window_menu_button_radius" /> + <solid android:color="@color/tv_window_menu_icon_bg" /> +</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml index 7a3ee23d8cdc..dcce4698c252 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml @@ -16,92 +16,46 @@ --> <!-- Layout for TvPipMenuView --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/tv_pip_menu" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center|top"> + android:id="@+id/tv_pip_menu" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center|top"> <!-- Matches the PiP app content --> - <View + <FrameLayout android:id="@+id/tv_pip" android:layout_width="0dp" android:layout_height="0dp" - android:alpha="0" - android:background="@color/tv_pip_menu_background" android:layout_marginTop="@dimen/pip_menu_outer_space" android:layout_marginStart="@dimen/pip_menu_outer_space" - android:layout_marginEnd="@dimen/pip_menu_outer_space"/> + android:layout_marginEnd="@dimen/pip_menu_outer_space"> - <ScrollView - android:id="@+id/tv_pip_menu_scroll" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignTop="@+id/tv_pip" - android:layout_alignStart="@+id/tv_pip" - android:layout_alignEnd="@+id/tv_pip" - android:layout_alignBottom="@+id/tv_pip" - android:scrollbars="none" - android:visibility="gone"/> + <View + android:id="@+id/tv_pip_menu_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/tv_pip_menu_background" + android:alpha="0"/> - <HorizontalScrollView - android:id="@+id/tv_pip_menu_horizontal_scroll" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignTop="@+id/tv_pip" - android:layout_alignStart="@+id/tv_pip" - android:layout_alignEnd="@+id/tv_pip" - android:layout_alignBottom="@+id/tv_pip" - android:scrollbars="none"> + <View + android:id="@+id/tv_pip_menu_dim_layer" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/tv_pip_menu_dim_layer" + android:alpha="0"/> - <LinearLayout + <com.android.internal.widget.RecyclerView android:id="@+id/tv_pip_menu_action_buttons" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal" - android:alpha="0"> - - <Space - android:layout_width="@dimen/pip_menu_button_wrapper_margin" - android:layout_height="@dimen/pip_menu_button_wrapper_margin"/> - - <com.android.wm.shell.pip.tv.TvPipMenuActionButton - android:id="@+id/tv_pip_menu_fullscreen_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/pip_ic_fullscreen_white" - android:text="@string/pip_fullscreen" /> - - <com.android.wm.shell.pip.tv.TvPipMenuActionButton - android:id="@+id/tv_pip_menu_close_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/pip_ic_close_white" - android:text="@string/pip_close" /> - - <!-- More TvPipMenuActionButtons may be added here at runtime. --> - - <com.android.wm.shell.pip.tv.TvPipMenuActionButton - android:id="@+id/tv_pip_menu_move_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/pip_ic_move_white" - android:text="@string/pip_move" /> - - <com.android.wm.shell.pip.tv.TvPipMenuActionButton - android:id="@+id/tv_pip_menu_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/pip_ic_collapse" - android:visibility="gone" - android:text="@string/pip_collapse" /> - - <Space - android:layout_width="@dimen/pip_menu_button_wrapper_margin" - android:layout_height="@dimen/pip_menu_button_wrapper_margin"/> - - </LinearLayout> - </HorizontalScrollView> + android:layout_gravity="center" + android:padding="@dimen/pip_menu_button_start_end_offset" + android:clipToPadding="false" + android:alpha="0" + android:contentDescription="@string/a11y_pip_menu_entered"/> + </FrameLayout> + <!-- Frame around the content, just overlapping the corners to make them round --> <View android:id="@+id/tv_pip_border" android:layout_width="0dp" @@ -111,8 +65,9 @@ android:layout_marginEnd="@dimen/pip_menu_outer_space_frame" android:background="@drawable/tv_pip_menu_border"/> + <!-- Temporarily extending the background to show an edu text hint for opening the menu --> <FrameLayout - android:id="@+id/tv_pip_menu_edu_text_container" + android:id="@+id/tv_pip_menu_edu_text_drawer_placeholder" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/tv_pip" @@ -120,23 +75,10 @@ android:layout_alignStart="@+id/tv_pip" android:layout_alignEnd="@+id/tv_pip" android:background="@color/tv_pip_menu_background" - android:clipChildren="true"> - - <TextView - android:id="@+id/tv_pip_menu_edu_text" - android:layout_width="wrap_content" - android:layout_height="@dimen/pip_menu_edu_text_view_height" - android:layout_gravity="bottom|center" - android:gravity="center" - android:paddingBottom="@dimen/pip_menu_border_width" - android:text="@string/pip_edu_text" - android:singleLine="true" - android:ellipsize="marquee" - android:marqueeRepeatLimit="1" - android:scrollHorizontally="true" - android:textAppearance="@style/TvPipEduText"/> - </FrameLayout> + android:paddingBottom="@dimen/pip_menu_border_width" + android:paddingTop="@dimen/pip_menu_border_width"/> + <!-- Frame around the PiP content + edu text hint - used to highlight open menu --> <View android:id="@+id/tv_pip_menu_frame" android:layout_width="match_parent" @@ -144,6 +86,17 @@ android:layout_margin="@dimen/pip_menu_outer_space_frame" android:background="@drawable/tv_pip_menu_border"/> + <!-- Move menu --> + <com.android.wm.shell.common.TvWindowMenuActionButton + android:id="@+id/tv_pip_menu_done_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:src="@drawable/pip_ic_close_white" + android:visibility="gone" + android:text="@string/a11y_action_pip_move_done" /> + <ImageView android:id="@+id/tv_pip_menu_arrow_up" android:layout_width="@dimen/pip_menu_arrow_size" @@ -151,6 +104,7 @@ android:layout_centerHorizontal="true" android:layout_alignParentTop="true" android:alpha="0" + android:contentDescription="@string/a11y_action_pip_move_up" android:elevation="@dimen/pip_menu_arrow_elevation" android:src="@drawable/pip_ic_move_up" /> @@ -161,6 +115,7 @@ android:layout_centerVertical="true" android:layout_alignParentRight="true" android:alpha="0" + android:contentDescription="@string/a11y_action_pip_move_right" android:elevation="@dimen/pip_menu_arrow_elevation" android:src="@drawable/pip_ic_move_right" /> @@ -171,6 +126,7 @@ android:layout_centerHorizontal="true" android:layout_alignParentBottom="true" android:alpha="0" + android:contentDescription="@string/a11y_action_pip_move_down" android:elevation="@dimen/pip_menu_arrow_elevation" android:src="@drawable/pip_ic_move_down" /> @@ -181,6 +137,7 @@ android:layout_centerVertical="true" android:layout_alignParentLeft="true" android:alpha="0" + android:contentDescription="@string/a11y_action_pip_move_left" android:elevation="@dimen/pip_menu_arrow_elevation" android:src="@drawable/pip_ic_move_left" /> </RelativeLayout> diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml deleted file mode 100644 index db96d8de4094..000000000000 --- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2020 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. ---> -<!-- Layout for TvPipMenuActionButton --> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/button" - android:layout_width="@dimen/pip_menu_button_size" - android:layout_height="@dimen/pip_menu_button_size" - android:padding="@dimen/pip_menu_button_margin" - android:stateListAnimator="@animator/tv_pip_menu_action_button_animator" - android:focusable="true"> - - <View android:id="@+id/background" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center" - android:duplicateParentState="true" - android:background="@drawable/tv_pip_button_bg"/> - - <ImageView android:id="@+id/icon" - android:layout_width="@dimen/pip_menu_icon_size" - android:layout_height="@dimen/pip_menu_icon_size" - android:layout_gravity="center" - android:duplicateParentState="true" - android:tint="@color/tv_pip_menu_icon" /> -</FrameLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml b/libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml new file mode 100644 index 000000000000..e0fa59c9f157 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml @@ -0,0 +1,125 @@ +<!-- + ~ Copyright (C) 2022 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. + --> +<!-- Layout for TvSplitMenuView --> +<com.android.wm.shell.splitscreen.tv.TvSplitMenuView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent"> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center"> + + <LinearLayout + android:id="@+id/tv_split_main_menu" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center"> + + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:gravity="center"> + + <com.android.wm.shell.common.TvWindowMenuActionButton + android:id="@+id/tv_split_main_menu_focus_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/tv_split_menu_ic_focus" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:gravity="center"> + + <com.android.wm.shell.common.TvWindowMenuActionButton + android:id="@+id/tv_split_main_menu_close_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/pip_ic_close_white" /> + + </LinearLayout> + + </LinearLayout> + + <com.android.wm.shell.common.TvWindowMenuActionButton + android:id="@+id/tv_split_menu_swap_stages" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/tv_split_menu_ic_swap" /> + + <LinearLayout + android:id="@+id/tv_split_side_menu" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center"> + + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:gravity="center"> + + <com.android.wm.shell.common.TvWindowMenuActionButton + android:id="@+id/tv_split_side_menu_focus_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/tv_split_menu_ic_focus" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:gravity="center"> + + <com.android.wm.shell.common.TvWindowMenuActionButton + android:id="@+id/tv_split_side_menu_close_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/pip_ic_close_white" /> + + </LinearLayout> + + </LinearLayout> + + </LinearLayout> +</com.android.wm.shell.splitscreen.tv.TvSplitMenuView> diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml new file mode 100644 index 000000000000..b2ac85b018be --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 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. + --> +<!-- Layout for TvWindowMenuActionButton --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/button" + android:layout_width="@dimen/tv_window_menu_button_size" + android:layout_height="@dimen/tv_window_menu_button_size" + android:padding="@dimen/tv_window_menu_button_margin" + android:duplicateParentState="true" + android:stateListAnimator="@animator/tv_window_menu_action_button_animator" + android:focusable="true"> + + <View android:id="@+id/background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:duplicateParentState="true" + android:background="@drawable/tv_window_button_bg"/> + + <ImageView android:id="@+id/icon" + android:layout_width="@dimen/tv_window_menu_icon_size" + android:layout_height="@dimen/tv_window_menu_icon_size" + android:layout_gravity="center" + android:duplicateParentState="true" + android:tint="@color/tv_window_menu_icon" /> +</FrameLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 2476f65c7e5b..904ae860d76d 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Instellings"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Gaan by verdeelde skerm in"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Kieslys"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Prent-in-prent-kieslys"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in beeld-in-beeld"</string> <string name="pip_notification_message" msgid="8854051911700302620">"As jy nie wil hê dat <xliff:g id="NAME">%s</xliff:g> hierdie kenmerk moet gebruik nie, tik om instellings oop te maak en skakel dit af."</string> <string name="pip_play" msgid="3496151081459417097">"Speel"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Laat los"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Program sal dalk nie met verdeelde skerm werk nie."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Program steun nie verdeelde skerm nie."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Hierdie app kan net in 1 venster oopgemaak word."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Program sal dalk nie op \'n sekondêre skerm werk nie."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Program steun nie begin op sekondêre skerms nie."</string> <string name="accessibility_divider" msgid="703810061635792791">"Skermverdeler"</string> + <string name="divider_title" msgid="5482989479865361192">"Skermverdeler"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Volskerm links"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Links 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Links 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Borrel"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Bestuur"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Borrel is toegemaak."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tik om hierdie program te herbegin en maak volskerm oop."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tik om hierdie program te herbegin vir ’n beter aansig."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerakwessies?\nTik om aan te pas"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nie opgelos nie?\nTik om terug te stel"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen kamerakwessies nie? Tik om toe te maak."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Sommige programme werk beter in portret"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Probeer een van hierdie opsies om jou spasie ten beste te benut"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Draai jou toestel om dit volskerm te maak"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dubbeltik langs ’n program om dit te herposisioneer"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sien en doen meer"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Sleep ’n ander program in vir verdeelde skerm"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Maak klein"</string> + <string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string> + <string name="back_button_text" msgid="1469718707134137085">"Terug"</string> + <string name="handle_text" msgid="1766582106752184456">"Handvatsel"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Volskerm"</string> + <string name="desktop_text" msgid="1077633567027630454">"Rekenaarmodus"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Verdeelde skerm"</string> + <string name="more_button_text" msgid="3655388105592893530">"Meer"</string> + <string name="float_button_text" msgid="9221657008391364581">"Sweef"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-af/strings_tv.xml b/libs/WindowManager/Shell/res/values-af/strings_tv.xml index c87bec093cca..2254fc9beb11 100644 --- a/libs/WindowManager/Shell/res/values-af/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-af/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Beeld-in-beeld"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Titellose program)"</string> - <string name="pip_close" msgid="9135220303720555525">"Maak PIP toe"</string> + <string name="pip_close" msgid="2955969519031223530">"Maak toe"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Volskerm"</string> - <string name="pip_move" msgid="1544227837964635439">"Skuif PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Vou PIP uit"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Vou PIP in"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Dubbeldruk "<annotation icon="home_icon">" TUIS "</annotation>" vir kontroles"</string> + <string name="pip_move" msgid="158770205886688553">"Skuif"</string> + <string name="pip_expand" msgid="1051966011679297308">"Vou uit"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Vou in"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Dubbeldruk "<annotation icon="home_icon">"TUIS"</annotation>" vir kontroles"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Prent-in-prent-kieslys"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Skuif links"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Skuif regs"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Skuif op"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Skuif af"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Klaar"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index f0c391cd6b99..8b467041ed5f 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -22,7 +22,8 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"ቅንብሮች"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"የተከፈለ ማያ ገጽን አስገባ"</string> <string name="pip_menu_title" msgid="5393619322111827096">"ምናሌ"</string> - <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> በስዕል-ላይ-ስዕል ውስጥ ነው"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"የሥዕል-ላይ-ሥዕል ምናሌ"</string> + <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> በሥዕል-ላይ-ሥዕል ውስጥ ነው"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ይህን ባህሪ እንዲጠቀም ካልፈለጉ ቅንብሮችን ለመክፈት መታ ያድርጉና ያጥፉት።"</string> <string name="pip_play" msgid="3496151081459417097">"አጫውት"</string> <string name="pip_pause" msgid="690688849510295232">"ባለበት አቁም"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው።"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string> <string name="accessibility_divider" msgid="703810061635792791">"የተከፈለ የማያ ገጽ ከፋይ"</string> + <string name="divider_title" msgid="5482989479865361192">"የተከፈለ የማያ ገጽ ከፋይ"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"የግራ ሙሉ ማያ ገጽ"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ግራ 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ግራ 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"አረፋ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ያቀናብሩ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"አረፋ ተሰናብቷል።"</string> - <string name="restart_button_description" msgid="5887656107651190519">"ይህን መተግበሪያ ዳግም ለማስነሳት መታ ያድርጉ እና ወደ ሙሉ ማያ ገጽ ይሂዱ።"</string> + <string name="restart_button_description" msgid="6712141648865547958">"ለተሻለ ዕይታ ይህን መተግበሪያ ዳግም ለማስነሳት መታ ያድርጉ።"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"አንዳንድ መተግበሪያዎች በቁም ፎቶ ውስጥ በተሻለ ሁኔታ ይሰራሉ"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ቦታዎን በአግባቡ ለመጠቀም ከእነዚህ አማራጮች ውስጥ አንዱን ይሞክሩ"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ወደ የሙሉ ገጽ ዕይታ ለመሄድ መሣሪያዎን ያሽከርክሩት"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ቦታውን ለመቀየር ከመተግበሪያው ቀጥሎ ላይ ሁለቴ መታ ያድርጉ"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ተጨማሪ ይመልከቱ እና ያድርጉ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ለተከፈለ ማያ ገጽ ሌላ መተግበሪያ ይጎትቱ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጪ ሁለቴ መታ ያድርጉ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string> + <string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string> + <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string> + <string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string> + <string name="handle_text" msgid="1766582106752184456">"መያዣ"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"ሙሉ ማያ"</string> + <string name="desktop_text" msgid="1077633567027630454">"የዴስክቶፕ ሁነታ"</string> + <string name="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገጽ"</string> + <string name="more_button_text" msgid="3655388105592893530">"ተጨማሪ"</string> + <string name="float_button_text" msgid="9221657008391364581">"ተንሳፋፊ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml index d23353858de6..a6be57889a4e 100644 --- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml @@ -17,12 +17,18 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ስዕል-ላይ-ስዕል"</string> + <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ሥዕል-ላይ-ሥዕል"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ርዕስ የሌለው ፕሮግራም)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIPን ዝጋ"</string> + <string name="pip_close" msgid="2955969519031223530">"ዝጋ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</string> - <string name="pip_move" msgid="1544227837964635439">"ፒአይፒ ውሰድ"</string> - <string name="pip_expand" msgid="7605396312689038178">"ፒአይፒን ዘርጋ"</string> - <string name="pip_collapse" msgid="5732233773786896094">"ፒአይፒን ሰብስብ"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" ለመቆጣጠሪያዎች "<annotation icon="home_icon">"መነሻ"</annotation>"ን ሁለቴ ይጫኑ"</string> + <string name="pip_move" msgid="158770205886688553">"ውሰድ"</string> + <string name="pip_expand" msgid="1051966011679297308">"ዘርጋ"</string> + <string name="pip_collapse" msgid="3903295106641385962">"ሰብስብ"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"ለመቆጣጠሪያዎች "<annotation icon="home_icon">"መነሻ"</annotation>"ን ሁለቴ ይጫኑ"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"የሥዕል-ላይ-ሥዕል ምናሌ።"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ወደ ግራ ውሰድ"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ወደ ቀኝ ውሰድ"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ወደ ላይ ውሰድ"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ወደ ታች ውሰድ"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ተጠናቅቋል"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index aa4b3b704110..635334db0d64 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"الإعدادات"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"الدخول في وضع تقسيم الشاشة"</string> <string name="pip_menu_title" msgid="5393619322111827096">"القائمة"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"قائمة نافذة ضمن النافذة"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> يظهر في صورة داخل صورة"</string> <string name="pip_notification_message" msgid="8854051911700302620">"إذا كنت لا تريد أن يستخدم <xliff:g id="NAME">%s</xliff:g> هذه الميزة، فانقر لفتح الإعدادات، ثم أوقِف تفعيل هذه الميزة."</string> <string name="pip_play" msgid="3496151081459417097">"تشغيل"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"قد لا يعمل التطبيق بشكل سليم في وضع \"تقسيم الشاشة\"."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"التطبيق لا يتيح تقسيم الشاشة."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string> <string name="accessibility_divider" msgid="703810061635792791">"أداة تقسيم الشاشة"</string> + <string name="divider_title" msgid="5482989479865361192">"أداة تقسيم الشاشة"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"عرض النافذة اليسرى بملء الشاشة"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ضبط حجم النافذة اليسرى ليكون ٧٠%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ضبط حجم النافذة اليسرى ليكون ٥٠%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"فقاعة"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"إدارة"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"تم إغلاق الفقاعة."</string> - <string name="restart_button_description" msgid="5887656107651190519">"انقر لإعادة تشغيل هذا التطبيق والانتقال إلى وضع ملء الشاشة."</string> + <string name="restart_button_description" msgid="6712141648865547958">"انقر لإعادة تشغيل هذا التطبيق للحصول على عرض أفضل."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"تعمل بعض التطبيقات على أكمل وجه في الشاشات العمودية"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"جرِّب تنفيذ أحد هذه الخيارات للاستفادة من مساحتك إلى أقصى حد."</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"قم بتدوير الشاشة للانتقال إلى وضع ملء الشاشة."</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"انقر مرتين بجانب التطبيق لتغيير موضعه."</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"استخدام تطبيقات متعدّدة في وقت واحد"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"انقر مرّتين خارج تطبيق لتغيير موضعه."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"حسنًا"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"التوسيع للحصول على مزيد من المعلومات"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string> + <string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string> + <string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string> + <string name="back_button_text" msgid="1469718707134137085">"رجوع"</string> + <string name="handle_text" msgid="1766582106752184456">"مقبض"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"ملء الشاشة"</string> + <string name="desktop_text" msgid="1077633567027630454">"وضع سطح المكتب"</string> + <string name="split_screen_text" msgid="1396336058129570886">"تقسيم الشاشة"</string> + <string name="more_button_text" msgid="3655388105592893530">"المزيد"</string> + <string name="float_button_text" msgid="9221657008391364581">"نافذة عائمة"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml index a1ceda5fc987..82ab8e9ee15b 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"نافذة ضمن النافذة"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ليس هناك عنوان للبرنامج)"</string> - <string name="pip_close" msgid="9135220303720555525">"إغلاق PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"إغلاق"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ملء الشاشة"</string> - <string name="pip_move" msgid="1544227837964635439">"نقل نافذة داخل النافذة (PIP)"</string> - <string name="pip_expand" msgid="7605396312689038178">"توسيع نافذة داخل النافذة (PIP)"</string> - <string name="pip_collapse" msgid="5732233773786896094">"تصغير نافذة داخل النافذة (PIP)"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" انقر مرتين على "<annotation icon="home_icon">" الصفحة الرئيسية "</annotation>" للوصول لعناصر التحكم."</string> + <string name="pip_move" msgid="158770205886688553">"نقل"</string> + <string name="pip_expand" msgid="1051966011679297308">"توسيع"</string> + <string name="pip_collapse" msgid="3903295106641385962">"تصغير"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"انقر مرتين على "<annotation icon="home_icon">" الرئيسية "</annotation>" للوصول لعناصر التحكم."</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"قائمة نافذة ضمن النافذة"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"نقل لليسار"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"نقل لليمين"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"نقل للأعلى"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"نقل للأسفل"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"تمّ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index 985d3b9b96fd..788fd5c0597a 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"ছেটিং"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"বিভাজিত স্ক্ৰীন ম’ডলৈ যাওক"</string> <string name="pip_menu_title" msgid="5393619322111827096">"মেনু"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"চিত্ৰৰ ভিতৰৰ চিত্ৰ মেনু"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> চিত্ৰৰ ভিতৰৰ চিত্ৰত আছে"</string> <string name="pip_notification_message" msgid="8854051911700302620">"আপুনি যদি <xliff:g id="NAME">%s</xliff:g> সুবিধাটো ব্যৱহাৰ কৰিব নোখোজে, তেন্তে ছেটিং খুলিবলৈ টিপক আৰু তালৈ গৈ ইয়াক অফ কৰক।"</string> <string name="pip_play" msgid="3496151081459417097">"প্লে কৰক"</string> @@ -33,18 +34,20 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"দেখুৱাওক"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"এপ্টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে।"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"এপ্টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে।"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই এপ্টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি।"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"গৌণ ডিছপ্লেত এপে সঠিকভাৱে কাম নকৰিব পাৰে।"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string> <string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string> + <string name="divider_title" msgid="5482989479865361192">"বিভাজিত স্ক্ৰীনৰ বিভাজক"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাওঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string> - <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"বাওঁফালৰ স্ক্ৰীণখন ৭০% কৰক"</string> - <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীণখন ৫০% কৰক"</string> - <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"বাওঁফালৰ স্ক্ৰীণখন ৩০% কৰক"</string> + <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"বাওঁফালৰ স্ক্ৰীনখন ৭০% কৰক"</string> + <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীনখন ৫০% কৰক"</string> + <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"বাওঁফালৰ স্ক্ৰীনখন ৩০% কৰক"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"সোঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"শীৰ্ষ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string> - <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ স্ক্ৰীণখন ৭০% কৰক"</string> - <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীণখন ৫০% কৰক"</string> - <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীণখন ৩০% কৰক"</string> + <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ স্ক্ৰীনখন ৭০% কৰক"</string> + <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীনখন ৫০% কৰক"</string> + <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীনখন ৩০% কৰক"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"তলৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"পৰিচালনা কৰক"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল অগ্ৰাহ্য কৰা হৈছে"</string> - <string name="restart_button_description" msgid="5887656107651190519">"এপ্টো ৰিষ্টাৰ্ট কৰিবলৈ আৰু পূৰ্ণ স্ক্ৰীন ব্যৱহাৰ কৰিবলৈ টিপক।"</string> + <string name="restart_button_description" msgid="6712141648865547958">"উন্নত ভিউৰ বাবে এপ্টো ৰিষ্টাৰ্ট কৰিবলৈ টিপক।"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"কিছুমান এপে প’ৰ্ট্ৰেইট ম’ডত বেছি ভালকৈ কাম কৰে"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"আপোনাৰ spaceৰ পৰা পাৰ্যমানে উপকৃত হ’বলৈ ইয়াৰে এটা বিকল্প চেষ্টা কৰি চাওক"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"পূৰ্ণ স্ক্ৰীনলৈ যাবলৈ আপোনাৰ ডিভাইচটো ঘূৰাওক"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"এপ্টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ কাষত দুবাৰ টিপক"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"চাওক আৰু অধিক কৰক"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্ টানি আনি এৰক"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"এপ্টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ বাহিৰত দুবাৰ টিপক"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string> + <string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string> + <string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string> + <string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string> + <string name="handle_text" msgid="1766582106752184456">"হেণ্ডেল"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"সম্পূৰ্ণ স্ক্ৰীন"</string> + <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ ম’ড"</string> + <string name="split_screen_text" msgid="1396336058129570886">"বিভাজিত স্ক্ৰীন"</string> + <string name="more_button_text" msgid="3655388105592893530">"অধিক"</string> + <string name="float_button_text" msgid="9221657008391364581">"ওপঙা"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-as/strings_tv.xml b/libs/WindowManager/Shell/res/values-as/strings_tv.xml index 8d7bd9f6a27e..34eaaea33609 100644 --- a/libs/WindowManager/Shell/res/values-as/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-as/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"চিত্ৰৰ ভিতৰত চিত্ৰ"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিৰোনামবিহীন কাৰ্যক্ৰম)"</string> - <string name="pip_close" msgid="9135220303720555525">"পিপ বন্ধ কৰক"</string> + <string name="pip_close" msgid="2955969519031223530">"বন্ধ কৰক"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"সম্পূৰ্ণ স্ক্ৰীন"</string> - <string name="pip_move" msgid="1544227837964635439">"পিপ স্থানান্তৰ কৰক"</string> - <string name="pip_expand" msgid="7605396312689038178">"পিপ বিস্তাৰ কৰক"</string> - <string name="pip_collapse" msgid="5732233773786896094">"পিপ সংকোচন কৰক"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" নিয়ন্ত্ৰণৰ বাবে "<annotation icon="home_icon">" গৃহপৃষ্ঠা "</annotation>" বুটামত দুবাৰ হেঁচক"</string> + <string name="pip_move" msgid="158770205886688553">"স্থানান্তৰ কৰক"</string> + <string name="pip_expand" msgid="1051966011679297308">"বিস্তাৰ কৰক"</string> + <string name="pip_collapse" msgid="3903295106641385962">"সংকোচন কৰক"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"নিয়ন্ত্ৰণৰ বাবে "<annotation icon="home_icon">"গৃহপৃষ্ঠা"</annotation>" বুটামত দুবাৰ টিপক"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"চিত্ৰৰ ভিতৰৰ চিত্ৰ মেনু।"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"বাওঁফাললৈ নিয়ক"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"সোঁফাললৈ নিয়ক"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ওপৰলৈ নিয়ক"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"তললৈ নিয়ক"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"হ’ল"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index 8cd9b7a635ab..a56918d98592 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Ayarlar"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Bölünmüş ekrana daxil olun"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Şəkildə Şəkil Menyusu"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> şəkil içində şəkildədir"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> tətbiqinin bu funksiyadan istifadə etməyini istəmirsinizsə, ayarları açmaq və deaktiv etmək üçün klikləyin."</string> <string name="pip_play" msgid="3496151081459417097">"Oxudun"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Güvənli məkandan çıxarın"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Tətbiq bölünmüş ekran ilə işləməyə bilər."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Tətbiq ekran bölünməsini dəstəkləmir."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu tətbiq yalnız 1 pəncərədə açıla bilər."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Tətbiq ikinci ekranda işləməyə bilər."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Tətbiq ikinci ekranda başlamağı dəstəkləmir."</string> <string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcısı"</string> + <string name="divider_title" msgid="5482989479865361192">"Bölünmüş ekran ayırıcısı"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Sol tam ekran"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Sol 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sol 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Qabarcıq"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"İdarə edin"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Qabarcıqdan imtina edilib."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Bu tətbiqi sıfırlayaraq tam ekrana keçmək üçün toxunun."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Toxunaraq bu tətbiqi yenidən başladın ki, daha görüntü əldə edəsiniz."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera problemi var?\nBərpa etmək üçün toxunun"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Düzəltməmisiniz?\nGeri qaytarmaq üçün toxunun"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera problemi yoxdur? Qapatmaq üçün toxunun."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Bəzi tətbiqlər portret rejimində daha yaxşı işləyir"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Məkanınızdan maksimum yararlanmaq üçün bu seçimlərdən birini sınayın"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Tam ekrana keçmək üçün cihazınızı fırladın"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tətbiqin yerini dəyişmək üçün yanına iki dəfə toxunun"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ardını görün və edin"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Bölünmüş ekrandan istifadə etmək üçün başqa tətbiqi sürüşdürüb gətirin"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tətbiqin yerini dəyişmək üçün kənarına iki dəfə toxunun"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ətraflı məlumat üçün genişləndirin."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Kiçildin"</string> + <string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string> + <string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string> + <string name="handle_text" msgid="1766582106752184456">"Hər kəsə açıq istifadəçi adı"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string> + <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Rejimi"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string> + <string name="more_button_text" msgid="3655388105592893530">"Ardı"</string> + <string name="float_button_text" msgid="9221657008391364581">"Üzən pəncərə"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-az/strings_tv.xml b/libs/WindowManager/Shell/res/values-az/strings_tv.xml index 87c46fa41a01..c45a09645075 100644 --- a/libs/WindowManager/Shell/res/values-az/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-az/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Şəkil-içində-Şəkil"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıqsız proqram)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP bağlayın"</string> + <string name="pip_close" msgid="2955969519031223530">"Bağlayın"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP tətbiq edin"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP-ni genişləndirin"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP-ni yığcamlaşdırın"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Nizamlayıcılar üçün "<annotation icon="home_icon">" ƏSAS SƏHİFƏ "</annotation>" süçimini iki dəfə basın"</string> + <string name="pip_move" msgid="158770205886688553">"Köçürün"</string> + <string name="pip_expand" msgid="1051966011679297308">"Genişləndirin"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Yığcamlaşdırın"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Nizamlayıcılar üçün "<annotation icon="home_icon">"ƏSAS SƏHİFƏ "</annotation>" seçiminə iki dəfə basın"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Şəkildə şəkil menyusu."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Sola köçürün"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Sağa köçürün"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Yuxarı köçürün"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Aşağı köçürün"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Hazırdır"</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 49524c608543..dcb03aa0b365 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Podešavanja"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Uđi na podeljeni ekran"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meni slike u slici."</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je slika u slici"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ako ne želite da <xliff:g id="NAME">%s</xliff:g> koristi ovu funkciju, dodirnite da biste otvorili podešavanja i isključili je."</string> <string name="pip_play" msgid="3496151081459417097">"Pusti"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Uklonite iz tajne memorije"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi sa podeljenim ekranom."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podeljeni ekran."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija može da se otvori samo u jednom prozoru."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionisati na sekundarnom ekranu."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string> <string name="accessibility_divider" msgid="703810061635792791">"Razdelnik podeljenog ekrana"</string> + <string name="divider_title" msgid="5482989479865361192">"Razdelnik podeljenog ekrana"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Režim celog ekrana za levi ekran"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Levi ekran 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi ekran 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljajte"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da biste restartovali aplikaciju i prešli u režim celog ekrana."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite da biste restartovali ovu aplikaciju radi boljeg prikaza."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Imate problema sa kamerom?\nDodirnite da biste ponovo uklopili"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije rešen?\nDodirnite da biste vratili"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema sa kamerom? Dodirnite da biste odbacili."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Neke aplikacije najbolje funkcionišu u uspravnom režimu"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Isprobajte jednu od ovih opcija da biste na najbolji način iskoristili prostor"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotirajte uređaj za prikaz preko celog ekrana"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da biste promenili njenu poziciju"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vidite i uradite više"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Prevucite drugu aplikaciju da biste koristili podeljeni ekran"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste promenili njenu poziciju"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Umanjite"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string> + <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string> + <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Preko celog ekrana"</string> + <string name="desktop_text" msgid="1077633567027630454">"Režim za računare"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Podeljeni ekran"</string> + <string name="more_button_text" msgid="3655388105592893530">"Još"</string> + <string name="float_button_text" msgid="9221657008391364581">"Plutajuće"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml index c87f30611a07..6dc4ab1cea79 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Slika u slici"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string> - <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Zatvori"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ceo ekran"</string> - <string name="pip_move" msgid="1544227837964635439">"Premesti sliku u slici"</string> - <string name="pip_expand" msgid="7605396312689038178">"Proširi sliku u slici"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Skupi sliku u slici"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Dvaput pritisnite "<annotation icon="home_icon">" HOME "</annotation>" za kontrole"</string> + <string name="pip_move" msgid="158770205886688553">"Premesti"</string> + <string name="pip_expand" msgid="1051966011679297308">"Proširi"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Skupi"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Dvaput pritisnite "<annotation icon="home_icon">" HOME "</annotation>" za kontrole"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Meni Slika u slici."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Pomerite nalevo"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Pomerite nadesno"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Pomerite nagore"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pomerite nadole"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gotovo"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 1767e0d66241..f6b285a47f5a 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Налады"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Падзяліць экран"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню рэжыму \"Відарыс у відарысе\""</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> з’яўляецца відарысам у відарысе"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Калі вы не хочаце, каб праграма <xliff:g id="NAME">%s</xliff:g> выкарыстоўвала гэту функцыю, дакраніцеся, каб адкрыць налады і адключыць яе."</string> <string name="pip_play" msgid="3496151081459417097">"Прайграць"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Паказаць"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Праграма можа не працаваць у рэжыме падзеленага экрана."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Праграма не падтрымлівае функцыю дзялення экрана."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Гэту праграму можна адкрыць толькі ў адным акне."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Праграма можа не працаваць на дадатковых экранах."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Праграма не падтрымлівае запуск на дадатковых экранах."</string> <string name="accessibility_divider" msgid="703810061635792791">"Раздзяляльнік падзеленага экрана"</string> + <string name="divider_title" msgid="5482989479865361192">"Раздзяляльнік падзеленага экрана"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Левы экран – поўнаэкранны рэжым"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Левы экран – 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левы экран – 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Усплывальнае апавяшчэнне"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Кіраваць"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Усплывальнае апавяшчэнне адхілена."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Націсніце, каб перазапусціць гэту праграму і перайсці ў поўнаэкранны рэжым."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Націсніце, каб перазапусціць гэту праграму для лепшага прагляду."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Некаторыя праграмы лепш за ўсё працуюць у кніжнай арыентацыі"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Каб эфектыўна выкарыстоўваць прастору, паспрабуйце адзін з гэтых варыянтаў"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Каб перайсці ў поўнаэкранны рэжым, павярніце прыладу"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Двойчы націсніце побач з праграмай, каб перамясціць яе"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Адначасова выконвайце розныя задачы"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двойчы націсніце экран па-за праграмай, каб перамясціць яе"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Зразумела"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгарнуць для дадатковай інфармацыі"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string> + <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string> + <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> + <string name="handle_text" msgid="1766582106752184456">"Маркер"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"На ўвесь экран"</string> + <string name="desktop_text" msgid="1077633567027630454">"Рэжым працоўнага стала"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Падзяліць экран"</string> + <string name="more_button_text" msgid="3655388105592893530">"Яшчэ"</string> + <string name="float_button_text" msgid="9221657008391364581">"Зрабіць рухомым акном"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-be/strings_tv.xml b/libs/WindowManager/Shell/res/values-be/strings_tv.xml index 3566bc372820..20e725f1aa09 100644 --- a/libs/WindowManager/Shell/res/values-be/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-be/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Відарыс у відарысе"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Праграма без назвы)"</string> - <string name="pip_close" msgid="9135220303720555525">"Закрыць PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Закрыць"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Поўнаэкранны рэжым"</string> - <string name="pip_move" msgid="1544227837964635439">"Перамясціць PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Разгарнуць відарыс у відарысе"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Згарнуць відарыс у відарысе"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Двойчы націсніце "<annotation icon="home_icon">" ГАЛОЎНЫ ЭКРАН "</annotation>" для пераходу ў налады"</string> + <string name="pip_move" msgid="158770205886688553">"Перамясціць"</string> + <string name="pip_expand" msgid="1051966011679297308">"Разгарнуць"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Згарнуць"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Двойчы націсніце "<annotation icon="home_icon">"ГАЛОЎНЫ"</annotation>" для пераходу ў налады"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Меню рэжыму \"Відарыс у відарысе\"."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Перамясціць улева"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Перамясціць управа"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Перамясціць уверх"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Перамясціць уніз"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Гатова"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index c22fb86a4d4d..044f2a7438dd 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Настройки"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Преминаване към разделен екран"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню за режима „Картина в картината“"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> е в режима „Картина в картината“"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ако не искате <xliff:g id="NAME">%s</xliff:g> да използва тази функция, докоснете, за да отворите настройките, и я изключете."</string> <string name="pip_play" msgid="3496151081459417097">"Пускане"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Отмяна на съхраняването"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Приложението може да не работи в режим на разделен екран."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложението не поддържа разделен екран."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Това приложение може да се отвори само в 1 прозорец."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Възможно е приложението да не работи на алтернативни дисплеи."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложението не поддържа използването на алтернативни дисплеи."</string> <string name="accessibility_divider" msgid="703810061635792791">"Разделител в режима за разделен екран"</string> + <string name="divider_title" msgid="5482989479865361192">"Разделител в режима за разделен екран"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ляв екран: Показване на цял екран"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ляв екран: 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ляв екран: 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Управление"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отхвърлено."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Докоснете, за да рестартирате това приложение в режим на цял екран."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Докоснете, за да рестартирате това приложение с цел по-добър изглед."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Някои приложения работят най-добре във вертикален режим"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Изпробвайте една от следните опции, за да се възползвате максимално от мястото на екрана"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Завъртете екрана си, за да преминете в режим на цял екран"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Докоснете два пъти дадено приложение, за да промените позицията му"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Преглеждайте и правете повече неща"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Докоснете два пъти извън дадено приложение, за да промените позицията му"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Разбрах"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгъване за още информация."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string> + <string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string> + <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> + <string name="handle_text" msgid="1766582106752184456">"Манипулатор"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Цял екран"</string> + <string name="desktop_text" msgid="1077633567027630454">"Режим за настолни компютри"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Разделяне на екрана"</string> + <string name="more_button_text" msgid="3655388105592893530">"Още"</string> + <string name="float_button_text" msgid="9221657008391364581">"Плаващо"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml index 91049fd2cf02..e9906f952455 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Картина в картината"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без заглавие)"</string> - <string name="pip_close" msgid="9135220303720555525">"Затваряне на PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Затваряне"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Цял екран"</string> - <string name="pip_move" msgid="1544227837964635439">"„Картина в картина“: Преместв."</string> - <string name="pip_expand" msgid="7605396312689038178">"Разгъване на прозореца за PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Свиване на прозореца за PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" За достъп до контролите натиснете 2 пъти "<annotation icon="home_icon">"НАЧАЛО"</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Преместване"</string> + <string name="pip_expand" msgid="1051966011679297308">"Разгъване"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Свиване"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"За достъп до контролите натиснете два пъти "<annotation icon="home_icon">"НАЧАЛО"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Меню за функцията „Картина в картината“."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Преместване наляво"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Преместване надясно"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Преместване нагоре"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Преместване надолу"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Готово"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index c0944e0584e6..1fb0292bc56d 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"সেটিংস"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"\'স্প্লিট স্ক্রিন\' মোড চালু করুন"</string> <string name="pip_menu_title" msgid="5393619322111827096">"মেনু"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ছবির-মধ্যে-ছবি মেনু"</string> <string name="pip_notification_title" msgid="1347104727641353453">"ছবির-মধ্যে-ছবি তে <xliff:g id="NAME">%s</xliff:g> আছেন"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> কে এই বৈশিষ্ট্যটি ব্যবহার করতে দিতে না চাইলে ট্যাপ করে সেটিংসে গিয়ে সেটি বন্ধ করে দিন।"</string> <string name="pip_play" msgid="3496151081459417097">"চালান"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"আনস্ট্যাস করুন"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"অ্যাপটি স্প্লিট স্ক্রিনে কাজ নাও করতে পারে।"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"অ্যাপ্লিকেশান বিভক্ত-স্ক্রিন সমর্থন করে না৷"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই অ্যাপটি শুধু ১টি উইন্ডোয় খোলা যেতে পারে।"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"সেকেন্ডারি ডিসপ্লেতে অ্যাপ লঞ্চ করা যাবে না।"</string> <string name="accessibility_divider" msgid="703810061635792791">"বিভক্ত-স্ক্রিন বিভাজক"</string> + <string name="divider_title" msgid="5482989479865361192">"স্প্লিট স্ক্রিন বিভাজক"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাঁ দিকের অংশ নিয়ে পূর্ণ স্ক্রিন"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"৭০% বাকি আছে"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"৫০% বাকি আছে"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ম্যানেজ করুন"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল বাতিল করা হয়েছে।"</string> - <string name="restart_button_description" msgid="5887656107651190519">"এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন ও \'ফুল-স্ক্রিন\' মোড ব্যবহার করুন।"</string> + <string name="restart_button_description" msgid="6712141648865547958">"আরও ভাল ভিউয়ের জন্য এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন।"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"কিছু অ্যাপ \'পোর্ট্রেট\' মোডে সবচেয়ে ভাল কাজ করে"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"আপনার স্পেস সবচেয়ে ভালভাবে কাজে লাগাতে এইসব বিকল্পের মধ্যে কোনও একটি ব্যবহার করে দেখুন"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"\'ফুল স্ক্রিন\' মোডে যেতে ডিভাইস ঘোরান"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"কোনও অ্যাপের পাশে ডবল ট্যাপ করে সেটির জায়গা পরিবর্তন করুন"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"দেখুন ও আরও অনেক কিছু করুন"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"স্প্লিট স্ক্রিনের জন্য অন্য অ্যাপে টেনে আনুন"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"কোনও অ্যাপের স্থান পরিবর্তন করতে তার বাইরে ডবল ট্যাপ করুন"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুঝেছি"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"আরও তথ্যের জন্য বড় করুন।"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string> + <string name="minimize_button_text" msgid="271592547935841753">"ছোট করুন"</string> + <string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string> + <string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string> + <string name="handle_text" msgid="1766582106752184456">"হাতল"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"ফুলস্ক্রিন"</string> + <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ মোড"</string> + <string name="split_screen_text" msgid="1396336058129570886">"স্প্লিট স্ক্রিন"</string> + <string name="more_button_text" msgid="3655388105592893530">"আরও"</string> + <string name="float_button_text" msgid="9221657008391364581">"ফ্লোট"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml index 792708d128a5..b515154ec3e0 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ছবির-মধ্যে-ছবি"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিরোনামহীন প্রোগ্রাম)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP বন্ধ করুন"</string> + <string name="pip_close" msgid="2955969519031223530">"বন্ধ করুন"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"পূর্ণ স্ক্রিন"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP সরান"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP বড় করুন"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP আড়াল করুন"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" কন্ট্রোলের জন্য "<annotation icon="home_icon">" হোম "</annotation>" বোতামে ডবল প্রেস করুন"</string> + <string name="pip_move" msgid="158770205886688553">"সরান"</string> + <string name="pip_expand" msgid="1051966011679297308">"বড় করুন"</string> + <string name="pip_collapse" msgid="3903295106641385962">"আড়াল করুন"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"কন্ট্রোলের জন্য "<annotation icon="home_icon">" হোম "</annotation>" বোতামে ডবল প্রেস করুন"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ছবির-মধ্যে-ছবি মেনু।"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"বাঁদিকে সরান"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ডানদিকে সরান"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"উপরে তুলুন"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"নিচে নামান"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"হয়ে গেছে"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index ae01c641cc43..8e52d78f2f5f 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Postavke"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Otvori podijeljeni ekran"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meni načina rada slike u slici"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je u načinu priakza Slika u slici"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ako ne želite da <xliff:g id="NAME">%s</xliff:g> koristi ovu funkciju, dodirnite da otvorite postavke i isključite je."</string> <string name="pip_play" msgid="3496151081459417097">"Reproduciraj"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vađenje iz stasha"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi na podijeljenom ekranu."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava dijeljenje ekrana."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija se može otvoriti samo u 1 prozoru."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string> - <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik ekrana"</string> + <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog ekrana"</string> + <string name="divider_title" msgid="5482989479865361192">"Razdjelnik podijeljenog ekrana"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lijevo cijeli ekran"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Lijevo 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevo 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljaj"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da ponovo pokrenete ovu aplikaciju i aktivirate prikaz preko cijelog ekrana."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite da ponovo pokrenete ovu aplikaciju radi boljeg prikaza."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s kamerom?\nDodirnite da ponovo namjestite"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nije popravljeno?\nDodirnite da vratite"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nema problema s kamerom? Dodirnite da odbacite."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Određene aplikacije najbolje funkcioniraju u uspravnom načinu rada"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Isprobajte jednu od ovih opcija da maksimalno iskoristite prostor"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zarotirajte uređaj da aktivirate prikaz preko cijelog ekrana"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da promijenite njen položaj"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Pogledajte i učinite više"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Prevucite još jednu aplikaciju za podijeljeni ekran"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da promijenite njen položaj"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za više informacija."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimiziranje"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string> + <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string> + <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Cijeli ekran"</string> + <string name="desktop_text" msgid="1077633567027630454">"Način rada radne površine"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Podijeljeni ekran"</string> + <string name="more_button_text" msgid="3655388105592893530">"Više"</string> + <string name="float_button_text" msgid="9221657008391364581">"Lebdeći"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml index b7f0dca1b5a5..99e076b31180 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Slika u slici"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string> - <string name="pip_close" msgid="9135220303720555525">"Zatvori sliku u slici"</string> + <string name="pip_close" msgid="2955969519031223530">"Zatvori"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli ekran"</string> - <string name="pip_move" msgid="1544227837964635439">"Pokreni sliku u slici"</string> - <string name="pip_expand" msgid="7605396312689038178">"Proširi sliku u slici"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Suzi sliku u slici"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Dvaput pritisnite "<annotation icon="home_icon">" POČETNI EKRAN "</annotation>" za kontrole"</string> + <string name="pip_move" msgid="158770205886688553">"Premjesti"</string> + <string name="pip_expand" msgid="1051966011679297308">"Proširi"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Suzi"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Dvaput pritisnite "<annotation icon="home_icon">"POČETNI EKRAN"</annotation>" za kontrole"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Meni za način rada slika u slici."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Pomjeranje ulijevo"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Pomjeranje udesno"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Pomjeranje nagore"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pomjeranje nadolje"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gotovo"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index 8a522b3e6397..6bc4f99ee7ef 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Configuració"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Entra al mode de pantalla dividida"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú de pantalla en pantalla"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> està en pantalla en pantalla"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Si no vols que <xliff:g id="NAME">%s</xliff:g> utilitzi aquesta funció, toca per obrir la configuració i desactiva-la."</string> <string name="pip_play" msgid="3496151081459417097">"Reprodueix"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Deixa d\'amagar"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"És possible que l\'aplicació no funcioni amb la pantalla dividida."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'aplicació no admet la pantalla dividida."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aquesta aplicació només pot obrir-se en 1 finestra."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"És possible que l\'aplicació no funcioni en una pantalla secundària."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'aplicació no es pot obrir en pantalles secundàries."</string> <string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalles"</string> + <string name="divider_title" msgid="5482989479865361192">"Separador de pantalla dividida"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla esquerra completa"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Pantalla esquerra al 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pantalla esquerra al 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bombolla"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestiona"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"La bombolla s\'ha ignorat."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Toca per reiniciar aquesta aplicació i passar a pantalla completa."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Toca per reiniciar aquesta aplicació i obtenir una millor visualització."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tens problemes amb la càmera?\nToca per resoldre\'ls"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"El problema no s\'ha resolt?\nToca per desfer els canvis"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No tens cap problema amb la càmera? Toca per ignorar."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Algunes aplicacions funcionen millor en posició vertical"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prova una d\'aquestes opcions per treure el màxim profit de l\'espai"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gira el dispositiu per passar a pantalla completa"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Fes doble toc al costat d\'una aplicació per canviar-ne la posició"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta i fes més coses"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrossega una altra aplicació per utilitzar la pantalla dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Fes doble toc fora d\'una aplicació per canviar-ne la posició"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimitza"</string> + <string name="close_button_text" msgid="2913281996024033299">"Tanca"</string> + <string name="back_button_text" msgid="1469718707134137085">"Enrere"</string> + <string name="handle_text" msgid="1766582106752184456">"Ansa"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string> + <string name="desktop_text" msgid="1077633567027630454">"Mode d\'escriptori"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string> + <string name="more_button_text" msgid="3655388105592893530">"Més"</string> + <string name="float_button_text" msgid="9221657008391364581">"Flotant"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml index 1c560c7afa06..e261db9042bd 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pantalla en pantalla"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sense títol)"</string> - <string name="pip_close" msgid="9135220303720555525">"Tanca PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Tanca"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> - <string name="pip_move" msgid="1544227837964635439">"Mou pantalla en pantalla"</string> - <string name="pip_expand" msgid="7605396312689038178">"Desplega pantalla en pantalla"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Replega pantalla en pantalla"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Prem dos cops "<annotation icon="home_icon">" INICI "</annotation>" per accedir als controls"</string> + <string name="pip_move" msgid="158770205886688553">"Mou"</string> + <string name="pip_expand" msgid="1051966011679297308">"Desplega"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Replega"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Prem dos cops "<annotation icon="home_icon">"INICI"</annotation>" per accedir als controls"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de pantalla en pantalla."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mou cap a l\'esquerra"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mou cap a la dreta"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mou cap amunt"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mou cap avall"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Fet"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index d0cf80aef38c..b638b0e5179f 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Nastavení"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Aktivovat rozdělenou obrazovku"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Nabídka"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Nabídka režimu obrazu v obraze"</string> <string name="pip_notification_title" msgid="1347104727641353453">"Aplikace <xliff:g id="NAME">%s</xliff:g> je v režimu obraz v obraze"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Pokud nechcete, aby aplikace <xliff:g id="NAME">%s</xliff:g> tuto funkci používala, klepnutím otevřete nastavení a funkci vypněte."</string> <string name="pip_play" msgid="3496151081459417097">"Přehrát"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušit uložení"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikace v režimu rozdělené obrazovky nemusí fungovat."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikace nepodporuje režim rozdělené obrazovky."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tuto aplikaci lze otevřít jen na jednom okně."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikace na sekundárním displeji nemusí fungovat."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikace nepodporuje spuštění na sekundárních displejích."</string> <string name="accessibility_divider" msgid="703810061635792791">"Čára rozdělující obrazovku"</string> + <string name="divider_title" msgid="5482989479865361192">"Čára rozdělující obrazovku"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Levá část na celou obrazovku"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % vlevo"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % vlevo"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovat"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina byla zavřena."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Klepnutím aplikaci restartujete a přejdete na režim celé obrazovky"</string> + <string name="restart_button_description" msgid="6712141648865547958">"Klepnutím tuto aplikaci kvůli lepšímu zobrazení restartujete."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s fotoaparátem?\nKlepnutím vyřešíte"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepomohlo to?\nKlepnutím se vrátíte"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Žádné problémy s fotoaparátem? Klepnutím zavřete."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Některé aplikace fungují nejlépe na výšku"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Pokud chcete maximálně využít prostor, vyzkoušejte jednu z těchto možností"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Otočením zařízení přejděte do režimu celé obrazovky"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvojitým klepnutím vedle aplikace změňte její umístění"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lepší zobrazení a více možností"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Přetáhnutím druhé aplikace použijete rozdělenou obrazovku"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikaci změníte její umístění"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovat"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string> + <string name="back_button_text" msgid="1469718707134137085">"Zpět"</string> + <string name="handle_text" msgid="1766582106752184456">"Úchyt"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string> + <string name="desktop_text" msgid="1077633567027630454">"Režim počítače"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Rozdělená obrazovka"</string> + <string name="more_button_text" msgid="3655388105592893530">"Více"</string> + <string name="float_button_text" msgid="9221657008391364581">"Plovoucí"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml index 9a8cc2b4d70e..72e1ae907cfb 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Obraz v obraze"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Bez názvu)"</string> - <string name="pip_close" msgid="9135220303720555525">"Ukončit obraz v obraze (PIP)"</string> + <string name="pip_close" msgid="2955969519031223530">"Zavřít"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string> - <string name="pip_move" msgid="1544227837964635439">"Přesunout PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Rozbalit PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Sbalit PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Ovládací prvky zobrazíte dvojitým stisknutím "<annotation icon="home_icon">"tlačítka plochy"</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Přesunout"</string> + <string name="pip_expand" msgid="1051966011679297308">"Rozbalit"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Sbalit"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Ovládací prvky zobrazíte dvojitým stisknutím "<annotation icon="home_icon">"HOME"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Nabídka režimu obrazu v obraze"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Přesunout doleva"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Přesunout doprava"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Přesunout nahoru"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Přesunout dolů"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Hotovo"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index bb81c10c6e1b..e0b7a8cb44d7 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Indstillinger"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Åbn opdelt skærm"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu for integreret billede"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> vises som integreret billede"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Hvis du ikke ønsker, at <xliff:g id="NAME">%s</xliff:g> skal benytte denne funktion, kan du åbne indstillingerne og deaktivere den."</string> <string name="pip_play" msgid="3496151081459417097">"Afspil"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vis"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Appen fungerer muligvis ikke i opdelt skærm."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen understøtter ikke opdelt skærm."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne app kan kun åbnes i 1 vindue."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer muligvis ikke på sekundære skærme."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke åbnes på sekundære skærme."</string> <string name="accessibility_divider" msgid="703810061635792791">"Adskiller til opdelt skærm"</string> + <string name="divider_title" msgid="5482989479865361192">"Adskiller til opdelt skærm"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vis venstre del i fuld skærm"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Venstre 70 %"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Venstre 50 %"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Boble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen blev lukket."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tryk for at genstarte denne app, og gå til fuld skærm."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tryk for at genstarte denne app, så visningen forbedres."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du problemer med dit kamera?\nTryk for at gendanne det oprindelige format"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Løste det ikke problemet?\nTryk for at fortryde"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen problemer med dit kamera? Tryk for at afvise."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Nogle apps fungerer bedst i stående format"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prøv én af disse muligheder for at få mest muligt ud af dit rum"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Drej din enhed for at gå til fuld skærm"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tryk to gange ud for en app for at ændre dens placering"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gør mere"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Træk en anden app hertil for at bruge opdelt skærm"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryk to gange uden for en app for at justere dens placering"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string> + <string name="close_button_text" msgid="2913281996024033299">"Luk"</string> + <string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string> + <string name="handle_text" msgid="1766582106752184456">"Håndtag"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Fuld skærm"</string> + <string name="desktop_text" msgid="1077633567027630454">"Computertilstand"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Opdelt skærm"</string> + <string name="more_button_text" msgid="3655388105592893530">"Mere"</string> + <string name="float_button_text" msgid="9221657008391364581">"Svævende"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-da/strings_tv.xml b/libs/WindowManager/Shell/res/values-da/strings_tv.xml index cba660ac723c..5881b0674ad2 100644 --- a/libs/WindowManager/Shell/res/values-da/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-da/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Integreret billede"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uden titel)"</string> - <string name="pip_close" msgid="9135220303720555525">"Luk integreret billede"</string> + <string name="pip_close" msgid="2955969519031223530">"Luk"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Fuld skærm"</string> - <string name="pip_move" msgid="1544227837964635439">"Flyt PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Udvid PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Skjul PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Tryk to gange på "<annotation icon="home_icon">" HJEM "</annotation>" for at se indstillinger"</string> + <string name="pip_move" msgid="158770205886688553">"Flyt"</string> + <string name="pip_expand" msgid="1051966011679297308">"Udvid"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Skjul"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Tryk to gange på "<annotation icon="home_icon">"HJEM"</annotation>" for at se indstillinger"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu for integreret billede."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Flyt til venstre"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Flyt til højre"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Flyt op"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Flyt ned"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Udfør"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index c5d945a982ef..caca8b42154e 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Einstellungen"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"„Geteilter Bildschirm“ aktivieren"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menü „Bild im Bild“"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ist in Bild im Bild"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Wenn du nicht möchtest, dass <xliff:g id="NAME">%s</xliff:g> diese Funktion verwendet, tippe, um die Einstellungen zu öffnen und die Funktion zu deaktivieren."</string> <string name="pip_play" msgid="3496151081459417097">"Wiedergeben"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Die App funktioniert unter Umständen im Modus für geteilten Bildschirm nicht."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Das Teilen des Bildschirms wird in dieser App nicht unterstützt."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Diese App kann nur in einem einzigen Fenster geöffnet werden."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundären Display möglicherweise nicht."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Die App unterstützt den Start auf sekundären Displays nicht."</string> <string name="accessibility_divider" msgid="703810061635792791">"Bildschirmteiler"</string> + <string name="divider_title" msgid="5482989479865361192">"Bildschirmteiler"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vollbild links"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % links"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % links"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Verwalten"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble verworfen."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tippe, um die App im Vollbildmodus neu zu starten."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tippe, um diese App neu zu starten und die Ansicht zu verbessern."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Probleme mit der Kamera?\nZum Anpassen tippen."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Das Problem ist nicht behoben?\nZum Rückgängigmachen tippen."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Keine Probleme mit der Kamera? Zum Schließen tippen."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Einige Apps funktionieren am besten im Hochformat"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Mithilfe dieser Möglichkeiten kannst du dein Display optimal nutzen"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gerät drehen, um zum Vollbildmodus zu wechseln"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Neben einer App doppeltippen, um die Position zu ändern"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Mehr sehen und erledigen"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Weitere App hineinziehen, um den Bildschirm zu teilen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimieren"</string> + <string name="close_button_text" msgid="2913281996024033299">"Schließen"</string> + <string name="back_button_text" msgid="1469718707134137085">"Zurück"</string> + <string name="handle_text" msgid="1766582106752184456">"Ziehpunkt"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Vollbild"</string> + <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Geteilter Bildschirm"</string> + <string name="more_button_text" msgid="3655388105592893530">"Mehr"</string> + <string name="float_button_text" msgid="9221657008391364581">"Frei schwebend"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-de/strings_tv.xml b/libs/WindowManager/Shell/res/values-de/strings_tv.xml index 02a1b66eb63f..5c5cbda296a1 100644 --- a/libs/WindowManager/Shell/res/values-de/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-de/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Bild im Bild"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Kein Sendungsname gefunden)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP schließen"</string> + <string name="pip_close" msgid="2955969519031223530">"Schließen"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Vollbild"</string> - <string name="pip_move" msgid="1544227837964635439">"BiB verschieben"</string> - <string name="pip_expand" msgid="7605396312689038178">"BiB maximieren"</string> - <string name="pip_collapse" msgid="5732233773786896094">"BiB minimieren"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Für Steuerelemente zweimal "<annotation icon="home_icon">"STARTBILDSCHIRMTASTE"</annotation>" drücken"</string> + <string name="pip_move" msgid="158770205886688553">"Bewegen"</string> + <string name="pip_expand" msgid="1051966011679297308">"Maximieren"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Minimieren"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Für Steuerelemente 2× "<annotation icon="home_icon">"STARTBILDSCHIRMTASTE"</annotation>" drücken"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menü „Bild im Bild“."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Nach links bewegen"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Nach rechts bewegen"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Nach oben bewegen"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Nach unten bewegen"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Fertig"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index 70f55058925c..ffb4fb0169a9 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Ρυθμίσεις"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Μετάβαση σε διαχωρισμό οθόνης"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Μενού"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Μενού λειτουργίας Picture-in-Picture"</string> <string name="pip_notification_title" msgid="1347104727641353453">"Η λειτουργία picture-in-picture είναι ενεργή σε <xliff:g id="NAME">%s</xliff:g>."</string> <string name="pip_notification_message" msgid="8854051911700302620">"Εάν δεν θέλετε να χρησιμοποιείται αυτή η λειτουργία από την εφαρμογή <xliff:g id="NAME">%s</xliff:g>, πατήστε για να ανοίξετε τις ρυθμίσεις και απενεργοποιήστε την."</string> <string name="pip_play" msgid="3496151081459417097">"Αναπαραγωγή"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Κατάργηση απόκρυψης"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Αυτή η εφαρμογή μπορεί να ανοιχθεί μόνο σε 1 παράθυρο."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Η εφαρμογή δεν υποστηρίζει την εκκίνηση σε δευτερεύουσες οθόνες."</string> <string name="accessibility_divider" msgid="703810061635792791">"Διαχωριστικό οθόνης"</string> + <string name="divider_title" msgid="5482989479865361192">"Διαχωριστικό οθόνης"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Αριστερή πλήρης οθόνη"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Αριστερή 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Αριστερή 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Συννεφάκι"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Διαχείριση"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Το συννεφάκι παραβλέφθηκε."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Πατήστε για επανεκκίνηση αυτής της εφαρμογής και ενεργοποίηση πλήρους οθόνης."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Πατήστε για να επανεκκινήσετε αυτή την εφαρμογή για καλύτερη προβολή."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Ορισμένες εφαρμογές λειτουργούν καλύτερα σε κατακόρυφο προσανατολισμό"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Δοκιμάστε μία από αυτές τις επιλογές για να αξιοποιήσετε στο έπακρο τον χώρο σας."</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Περιστρέψτε τη συσκευή σας για μετάβαση σε πλήρη οθόνη."</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Πατήστε δύο φορές δίπλα σε μια εφαρμογή για να αλλάξετε τη θέση της."</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Δείτε και κάντε περισσότερα"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Πατήστε δύο φορές έξω από μια εφαρμογή για να αλλάξετε τη θέση της"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Το κατάλαβα"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ανάπτυξη για περισσότερες πληροφορίες."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Ελαχιστοποίηση"</string> + <string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string> + <string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string> + <string name="handle_text" msgid="1766582106752184456">"Λαβή"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Πλήρης οθόνη"</string> + <string name="desktop_text" msgid="1077633567027630454">"Λειτουργία επιφάνειας εργασίας"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Διαχωρισμός οθόνης"</string> + <string name="more_button_text" msgid="3655388105592893530">"Περισσότερα"</string> + <string name="float_button_text" msgid="9221657008391364581">"Κινούμενο"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-el/strings_tv.xml b/libs/WindowManager/Shell/res/values-el/strings_tv.xml index 24cd030cd754..a80e2c72de7e 100644 --- a/libs/WindowManager/Shell/res/values-el/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-el/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Δεν υπάρχει τίτλος προγράμματος)"</string> - <string name="pip_close" msgid="9135220303720555525">"Κλείσιμο PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Κλείσιμο"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Πλήρης οθόνη"</string> - <string name="pip_move" msgid="1544227837964635439">"Μετακίνηση PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Ανάπτυξη PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Σύμπτυξη PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Πατήστε δύο φορές "<annotation icon="home_icon">" ΑΡΧΙΚΗ ΟΘΟΝΗ "</annotation>" για στοιχεία ελέγχου"</string> + <string name="pip_move" msgid="158770205886688553">"Μετακίνηση"</string> + <string name="pip_expand" msgid="1051966011679297308">"Ανάπτυξη"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Σύμπτυξη"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Πατ. δύο φορές το κουμπί "<annotation icon="home_icon">"αρχ. οθ."</annotation>" για στ. ελέγχου"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Μενού λειτουργίας Picture-in-Picture."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Μετακίνηση αριστερά"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Μετακίνηση δεξιά"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Μετακίνηση επάνω"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Μετακίνηση κάτω"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Τέλος"</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 0b5aefa5c72e..c71011d3b359 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string> <string name="pip_play" msgid="3496151081459417097">"Play"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string> <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string> + <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Some apps work best in portrait"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string> + <string name="close_button_text" msgid="2913281996024033299">"Close"</string> + <string name="back_button_text" msgid="1469718707134137085">"Back"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string> + <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string> + <string name="more_button_text" msgid="3655388105592893530">"More"</string> + <string name="float_button_text" msgid="9221657008391364581">"Float"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml index 82257b42814d..71d02271090d 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> - <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Close"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> - <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Expand PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Collapse PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Double-press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</string> + <string name="pip_move" msgid="158770205886688553">"Move"</string> + <string name="pip_expand" msgid="1051966011679297308">"Expand"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Double-press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-picture menu"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Move down"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Done"</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 0b5aefa5c72e..05091fb11864 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-Picture Menu"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string> <string name="pip_play" msgid="3496151081459417097">"Play"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string> - <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string> + <string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string> + <string name="divider_title" msgid="5482989479865361192">"Split-screen divider"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string> @@ -64,21 +67,31 @@ <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string> <string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string> - <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles at any time"</string> + <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles anytime"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tap Manage to turn off bubbles from this app"</string> - <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> + <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Got it"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Some apps work best in portrait"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string> + <string name="close_button_text" msgid="2913281996024033299">"Close"</string> + <string name="back_button_text" msgid="1469718707134137085">"Back"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string> + <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string> + <string name="more_button_text" msgid="3655388105592893530">"More"</string> + <string name="float_button_text" msgid="9221657008391364581">"Float"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml index 82257b42814d..09def6b69f06 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml @@ -17,12 +17,18 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string> + <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> - <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Close"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> - <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Expand PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Collapse PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Double-press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</string> + <string name="pip_move" msgid="158770205886688553">"Move"</string> + <string name="pip_expand" msgid="1051966011679297308">"Expand"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Double press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-Picture menu."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Move down"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Done"</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 0b5aefa5c72e..c71011d3b359 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string> <string name="pip_play" msgid="3496151081459417097">"Play"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string> <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string> + <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Some apps work best in portrait"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string> + <string name="close_button_text" msgid="2913281996024033299">"Close"</string> + <string name="back_button_text" msgid="1469718707134137085">"Back"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string> + <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string> + <string name="more_button_text" msgid="3655388105592893530">"More"</string> + <string name="float_button_text" msgid="9221657008391364581">"Float"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml index 82257b42814d..71d02271090d 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> - <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Close"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> - <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Expand PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Collapse PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Double-press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</string> + <string name="pip_move" msgid="158770205886688553">"Move"</string> + <string name="pip_expand" msgid="1051966011679297308">"Expand"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Double-press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-picture menu"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Move down"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Done"</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 0b5aefa5c72e..c71011d3b359 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string> <string name="pip_play" msgid="3496151081459417097">"Play"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string> <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string> + <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Some apps work best in portrait"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string> + <string name="close_button_text" msgid="2913281996024033299">"Close"</string> + <string name="back_button_text" msgid="1469718707134137085">"Back"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string> + <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string> + <string name="more_button_text" msgid="3655388105592893530">"More"</string> + <string name="float_button_text" msgid="9221657008391364581">"Float"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml index 82257b42814d..71d02271090d 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> - <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Close"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> - <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Expand PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Collapse PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Double-press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</string> + <string name="pip_move" msgid="158770205886688553">"Move"</string> + <string name="pip_expand" msgid="1051966011679297308">"Expand"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Double-press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-picture menu"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Move down"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Done"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml index 5c3d0f65374a..2993fe7b81f4 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-Picture Menu"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string> <string name="pip_play" msgid="3496151081459417097">"Play"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string> <string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string> + <string name="divider_title" msgid="5482989479865361192">"Split-screen divider"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Some apps work best in portrait"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string> + <string name="close_button_text" msgid="2913281996024033299">"Close"</string> + <string name="back_button_text" msgid="1469718707134137085">"Back"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string> + <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string> + <string name="more_button_text" msgid="3655388105592893530">"More"</string> + <string name="float_button_text" msgid="9221657008391364581">"Float"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml index a6e494cfed3c..405770166274 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> - <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Close"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> - <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Expand PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Collapse PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Double press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</string> + <string name="pip_move" msgid="158770205886688553">"Move"</string> + <string name="pip_expand" msgid="1051966011679297308">"Expand"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Double press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-Picture menu."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Move down"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Done"</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 e523ae53b0cc..0eaca8ba040f 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Configuración"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Introducir pantalla dividida"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú de pantalla en pantalla"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está en modo de Pantalla en pantalla"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Si no quieres que <xliff:g id="NAME">%s</xliff:g> use esta función, presiona para abrir la configuración y desactivarla."</string> <string name="pip_play" msgid="3496151081459417097">"Reproducir"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Dejar de almacenar de manera segura"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la app no funcione en el modo de pantalla dividida."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La app no es compatible con la función de pantalla dividida."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app solo puede estar abierta en 1 ventana."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la app no funcione en una pantalla secundaria."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La app no puede iniciarse en pantallas secundarias."</string> <string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string> + <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla izquierda completa"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Izquierda: 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda: 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Cuadro"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Administrar"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Se descartó el cuadro."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Presiona para reiniciar esta app y acceder al modo de pantalla completa."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Presiona para reiniciar esta app y tener una mejor vista."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Tienes problemas con la cámara?\nPresiona para reajustarla"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se resolvió?\nPresiona para revertir los cambios"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No tienes problemas con la cámara? Presionar para descartar."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Algunas apps funcionan mejor en modo vertical"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prueba estas opciones para aprovechar al máximo tu espacio"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rota el dispositivo para ver la pantalla completa"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Presiona dos veces junto a una app para cambiar su posición"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Aprovecha más"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra otra app para el modo de pantalla dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Presiona dos veces fuera de una app para cambiar su ubicación"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expande para obtener más información."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> + <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string> + <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string> + <string name="handle_text" msgid="1766582106752184456">"Controlador"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string> + <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string> + <string name="more_button_text" msgid="3655388105592893530">"Más"</string> + <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml index 458f6b15b857..e0f3297ff966 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pantalla en pantalla"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sin título de programa)"</string> - <string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Cerrar"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> - <string name="pip_move" msgid="1544227837964635439">"Mover PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Maximizar PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Minimizar PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Presiona dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string> + <string name="pip_move" msgid="158770205886688553">"Mover"</string> + <string name="pip_expand" msgid="1051966011679297308">"Expandir"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Contraer"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Presiona dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de pantalla en pantalla"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover hacia la izquierda"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover hacia la derecha"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover hacia arriba"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover hacia abajo"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Listo"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index 974960708190..9c8fed17859b 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Ajustes"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Introducir pantalla dividida"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú de imagen en imagen"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está en imagen en imagen"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Si no quieres que <xliff:g id="NAME">%s</xliff:g> utilice esta función, toca la notificación para abrir los ajustes y desactivarla."</string> <string name="pip_play" msgid="3496151081459417097">"Reproducir"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"No esconder"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la aplicación no funcione con la pantalla dividida."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La aplicación no admite la pantalla dividida."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación solo puede abrirse en una ventana."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la aplicación no funcione en una pantalla secundaria."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La aplicación no se puede abrir en pantallas secundarias."</string> <string name="accessibility_divider" msgid="703810061635792791">"Dividir la pantalla"</string> + <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla izquierda completa"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Izquierda 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda 50%"</string> @@ -46,10 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Superior 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string> - <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usar Modo una mano"</string> + <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usar modo Una mano"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o toca cualquier zona que haya encima de la aplicación"</string> - <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar Modo una mano"</string> - <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Salir del Modo una mano"</string> + <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo Una mano"</string> + <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Salir del modo Una mano"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Ajustes de las burbujas de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú adicional"</string> <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Volver a añadir a la pila"</string> @@ -63,7 +66,7 @@ <string name="bubble_dismiss_text" msgid="8816558050659478158">"Cerrar burbuja"</string> <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar conversación en burbuja"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatea con burbujas"</string> - <string name="bubbles_user_education_description" msgid="4215862563054175407">"Las conversaciones nuevas aparecen como iconos flotantes llamadas \"burbujas\". Toca una burbuja para abrirla. Arrástrala para moverla."</string> + <string name="bubbles_user_education_description" msgid="4215862563054175407">"Las conversaciones nuevas aparecen como iconos flotantes llamados \"burbujas\". Toca una burbuja para abrirla. Arrástrala para moverla."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controla las burbujas"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Toca Gestionar para desactivar las burbujas de esta aplicación"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Burbuja"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionar"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbuja cerrada."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Toca para reiniciar esta aplicación e ir a la pantalla completa."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Toca para reiniciar esta aplicación y obtener una mejor vista."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Problemas con la cámara?\nToca para reajustar"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se ha solucionado?\nToca para revertir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No hay problemas con la cámara? Toca para cerrar."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Algunas aplicaciones funcionan mejor en vertical"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prueba una de estas opciones para sacar el máximo partido al espacio de tu pantalla"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gira el dispositivo para ir al modo de pantalla completa"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toca dos veces junto a una aplicación para cambiar su posición"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta más información y haz más"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra otra aplicación para activar la pantalla dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dos veces fuera de una aplicación para cambiarla de posición"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> + <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string> + <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string> + <string name="handle_text" msgid="1766582106752184456">"Controlador"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string> + <string name="desktop_text" msgid="1077633567027630454">"Modo Escritorio"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string> + <string name="more_button_text" msgid="3655388105592893530">"Más"</string> + <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml index 0a690984dac5..38be3effe356 100644 --- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Imagen en imagen"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sin título)"</string> - <string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Cerrar"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> - <string name="pip_move" msgid="1544227837964635439">"Mover imagen en imagen"</string> - <string name="pip_expand" msgid="7605396312689038178">"Mostrar imagen en imagen"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Ocultar imagen en imagen"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Pulsa dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string> + <string name="pip_move" msgid="158770205886688553">"Mover"</string> + <string name="pip_expand" msgid="1051966011679297308">"Mostrar"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Contraer"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Pulsa dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de imagen en imagen."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover hacia la izquierda"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover hacia la derecha"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover hacia arriba"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover hacia abajo"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Hecho"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index a5f82a6452c4..e8cbe5387410 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Seaded"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Ava jagatud ekraanikuva"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menüü"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menüü Pilt pildis"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> on režiimis Pilt pildis"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Kui te ei soovi, et rakendus <xliff:g id="NAME">%s</xliff:g> seda funktsiooni kasutaks, puudutage seadete avamiseks ja lülitage see välja."</string> <string name="pip_play" msgid="3496151081459417097">"Esita"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Eemalda hoidlast"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Rakendus ei pruugi poolitatud ekraaniga töötada."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Rakendus ei toeta jagatud ekraani."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Selle rakenduse saab avada ainult ühes aknas."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Rakendus ei pruugi teisesel ekraanil töötada."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Rakendus ei toeta teisestel ekraanidel käivitamist."</string> <string name="accessibility_divider" msgid="703810061635792791">"Ekraanijagaja"</string> + <string name="divider_title" msgid="5482989479865361192">"Ekraanijagaja"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vasak täisekraan"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vasak: 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasak: 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Mull"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Halda"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Mullist loobuti."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Puudutage rakenduse taaskäivitamiseks ja täisekraanrežiimi aktiveerimiseks."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Puudutage, et see rakendus parema vaate jaoks taaskäivitada."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kas teil on kaameraprobleeme?\nPuudutage ümberpaigutamiseks."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Kas probleemi ei lahendatud?\nPuudutage ennistamiseks."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kas kaameraprobleeme pole? Puudutage loobumiseks."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Mõni rakendus töötab kõige paremini vertikaalpaigutuses"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Proovige ühte neist valikutest, et oma ruumi parimal moel kasutada"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pöörake seadet, et aktiveerida täisekraanirežiim"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Topeltpuudutage rakenduse kõrval, et selle asendit muuta"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vaadake ja tehke rohkem"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Lohistage muusse rakendusse, et jagatud ekraanikuva kasutada"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Topeltpuudutage rakendusest väljaspool, et selle asendit muuta"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimeeri"</string> + <string name="close_button_text" msgid="2913281996024033299">"Sule"</string> + <string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string> + <string name="handle_text" msgid="1766582106752184456">"Käepide"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Täisekraan"</string> + <string name="desktop_text" msgid="1077633567027630454">"Lauaarvuti režiim"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Jagatud ekraanikuva"</string> + <string name="more_button_text" msgid="3655388105592893530">"Rohkem"</string> + <string name="float_button_text" msgid="9221657008391364581">"Hõljuv"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-et/strings_tv.xml b/libs/WindowManager/Shell/res/values-et/strings_tv.xml index dc0232303a70..a93cee51ce07 100644 --- a/libs/WindowManager/Shell/res/values-et/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-et/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pilt pildis"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programmi pealkiri puudub)"</string> - <string name="pip_close" msgid="9135220303720555525">"Sule PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Sule"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Täisekraan"</string> - <string name="pip_move" msgid="1544227837964635439">"Teisalda PIP-režiimi"</string> - <string name="pip_expand" msgid="7605396312689038178">"Laienda PIP-akent"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Ahenda PIP-aken"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Nuppude nägemiseks vajutage 2 korda nuppu "<annotation icon="home_icon">"AVAKUVA"</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Teisalda"</string> + <string name="pip_expand" msgid="1051966011679297308">"Laienda"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Ahenda"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Nuppude nägemiseks vajutage 2 korda nuppu "<annotation icon="home_icon">"AVAKUVA"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menüü Pilt pildis."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Teisalda vasakule"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Teisalda paremale"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Teisalda üles"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Teisalda alla"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Valmis"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index caa335a96222..4417668657e2 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -22,8 +22,9 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Ezarpenak"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Sartu pantaila zatituan"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menua"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Pantaila txiki gainjarriaren menua"</string> <string name="pip_notification_title" msgid="1347104727641353453">"Pantaila txiki gainjarrian dago <xliff:g id="NAME">%s</xliff:g>"</string> - <string name="pip_notification_message" msgid="8854051911700302620">"Ez baduzu nahi <xliff:g id="NAME">%s</xliff:g> zerbitzuak eginbide hori erabiltzea, sakatu hau ezarpenak ireki eta aukera desaktibatzeko."</string> + <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> zerbitzuak eginbide hori erabiltzea nahi ez baduzu, sakatu hau ezarpenak ireki eta aukera desaktibatzeko."</string> <string name="pip_play" msgid="3496151081459417097">"Erreproduzitu"</string> <string name="pip_pause" msgid="690688849510295232">"Pausatu"</string> <string name="pip_skip_to_next" msgid="8403429188794867653">"Joan hurrengora"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikazioak ez du onartzen pantaila zatitua"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Leiho bakar batean ireki daiteke aplikazioa."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string> <string name="accessibility_divider" msgid="703810061635792791">"Pantaila-zatitzailea"</string> + <string name="divider_title" msgid="5482989479865361192">"Pantaila-zatitzailea"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ezarri ezkerraldea pantaila osoan"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ezarri ezkerraldea % 70en"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ezarri ezkerraldea % 50en"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Burbuila"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Kudeatu"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Baztertu da globoa."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Saka ezazu aplikazioa berrabiarazteko, eta ezarri pantaila osoko modua."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Hobeto ikusteko, sakatu hau aplikazioa berrabiarazteko."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Arazoak dauzkazu kamerarekin?\nBerriro doitzeko, sakatu hau."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ez al da konpondu?\nLeheneratzeko, sakatu hau."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ez daukazu arazorik kamerarekin? Baztertzeko, sakatu hau."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Aplikazio batzuk orientazio bertikalean funtzionatzen dute hobekien"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Pantailako eremuari ahalik eta etekinik handiena ateratzeko, probatu aukera hauetakoren bat"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pantaila osoko modua erabiltzeko, biratu gailua"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren ondoko edozein toki"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ikusi eta egin gauza gehiago"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Pantaila zatituta ikusteko, arrastatu beste aplikazio bat"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren kanpoaldea"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Informazio gehiago lortzeko, zabaldu hau."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimizatu"</string> + <string name="close_button_text" msgid="2913281996024033299">"Itxi"</string> + <string name="back_button_text" msgid="1469718707134137085">"Atzera"</string> + <string name="handle_text" msgid="1766582106752184456">"Kontu-izena"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Pantaila osoa"</string> + <string name="desktop_text" msgid="1077633567027630454">"Ordenagailuetarako modua"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitua"</string> + <string name="more_button_text" msgid="3655388105592893530">"Gehiago"</string> + <string name="float_button_text" msgid="9221657008391364581">"Leiho gainerakorra"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml index bce06da2c66f..4b752fc9d1c4 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pantaila txiki gainjarria"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa izengabea)"</string> - <string name="pip_close" msgid="9135220303720555525">"Itxi PIPa"</string> + <string name="pip_close" msgid="2955969519031223530">"Itxi"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantaila osoa"</string> - <string name="pip_move" msgid="1544227837964635439">"Mugitu pantaila txiki gainjarria"</string> - <string name="pip_expand" msgid="7605396312689038178">"Zabaldu pantaila txiki gainjarria"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Tolestu pantaila txiki gainjarria"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Kontrolatzeko aukerak atzitzeko, sakatu birritan "<annotation icon="home_icon">" HASIERA "</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Mugitu"</string> + <string name="pip_expand" msgid="1051966011679297308">"Zabaldu"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Tolestu"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Kontrolatzeko aukerak atzitzeko, sakatu birritan "<annotation icon="home_icon">"HASIERA"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Pantaila txiki gainjarriaren menua."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Eraman ezkerrera"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Eraman eskuinera"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Eraman gora"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Eraman behera"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Eginda"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index 761fb9ddeb2f..7375faf8b30f 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"تنظیمات"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"ورود به حالت «صفحهٔ دونیمه»"</string> <string name="pip_menu_title" msgid="5393619322111827096">"منو"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"منو تصویر در تصویر"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> درحالت تصویر در تصویر است"</string> <string name="pip_notification_message" msgid="8854051911700302620">"اگر نمیخواهید <xliff:g id="NAME">%s</xliff:g> از این قابلیت استفاده کند، با ضربه زدن، تنظیمات را باز کنید و آن را خاموش کنید."</string> <string name="pip_play" msgid="3496151081459417097">"پخش"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"لغو مخفیسازی"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن است برنامه با «صفحهٔ دونیمه» کار نکند."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"برنامه از تقسیم صفحه پشتیبانی نمیکند."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"این برنامه فقط در ۱ پنجره میتواند باز شود."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن است برنامه در نمایشگر ثانویه کار نکند."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"برنامه از راهاندازی در نمایشگرهای ثانویه پشتیبانی نمیکند."</string> <string name="accessibility_divider" msgid="703810061635792791">"تقسیمکننده صفحه"</string> + <string name="divider_title" msgid="5482989479865361192">"تقسیمکننده صفحهٔ دونیمه"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"تمامصفحه چپ"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"٪۷۰ چپ"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"٪۵۰ چپ"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"حباب"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"مدیریت"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"حبابک رد شد."</string> - <string name="restart_button_description" msgid="5887656107651190519">"برای بازراهاندازی این برنامه و تغییر به حالت تمامصفحه، ضربه بزنید."</string> + <string name="restart_button_description" msgid="6712141648865547958">"برای داشتن نمایی بهتر، ضربه بزنید تا این برنامه بازراهاندازی شود."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"برخیاز برنامهها در حالت عمودی عملکرد بهتری دارند"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"با امتحان کردن یکی از این گزینهها، بیشترین بهره را از فضایتان ببرید"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"برای رفتن به حالت تمام صفحه، دستگاهتان را بچرخانید"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"در کنار برنامه دوضربه بزنید تا جابهجا شود"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه بهطور همزمان استفاده کنید"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"برای حالت صفحهٔ دونیمه، در برنامهای دیگر بکشید"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابهجا کردن برنامه، بیرون از آن دوضربه بزنید"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجهام"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string> + <string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string> + <string name="close_button_text" msgid="2913281996024033299">"بستن"</string> + <string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string> + <string name="handle_text" msgid="1766582106752184456">"دستگیره"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"تمامصفحه"</string> + <string name="desktop_text" msgid="1077633567027630454">"حالت رایانه"</string> + <string name="split_screen_text" msgid="1396336058129570886">"صفحهٔ دونیمه"</string> + <string name="more_button_text" msgid="3655388105592893530">"بیشتر"</string> + <string name="float_button_text" msgid="9221657008391364581">"شناور"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml index ff9a03c6cefb..55394cbdc31a 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"تصویر در تصویر"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بدون عنوان)"</string> - <string name="pip_close" msgid="9135220303720555525">"بستن PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"بستن"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"تمام صفحه"</string> - <string name="pip_move" msgid="1544227837964635439">"انتقال PIP (تصویر در تصویر)"</string> - <string name="pip_expand" msgid="7605396312689038178">"گسترده کردن «تصویر در تصویر»"</string> - <string name="pip_collapse" msgid="5732233773786896094">"جمع کردن «تصویر در تصویر»"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" برای کنترلها، دکمه "<annotation icon="home_icon">"صفحه اصلی"</annotation>" را دوبار فشار دهید"</string> + <string name="pip_move" msgid="158770205886688553">"انتقال"</string> + <string name="pip_expand" msgid="1051966011679297308">"گسترده کردن"</string> + <string name="pip_collapse" msgid="3903295106641385962">"جمع کردن"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"برای کنترلها، دکمه "<annotation icon="home_icon">"صفحه اصلی"</annotation>" را دوبار فشار دهید"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"منوی تصویر در تصویر."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"انتقال بهچپ"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"انتقال بهراست"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"انتقال بهبالا"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"انتقال بهپایین"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"تمام"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index c809b4879e71..7729d1c62f30 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Asetukset"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Avaa jaettu näyttö"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Valikko"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Kuva kuvassa ‑valikko"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> on kuva kuvassa ‑tilassa"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Jos et halua, että <xliff:g id="NAME">%s</xliff:g> voi käyttää tätä ominaisuutta, avaa asetukset napauttamalla ja poista se käytöstä."</string> <string name="pip_play" msgid="3496151081459417097">"Toista"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poista turvasäilytyksestä"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Sovellus ei ehkä toimi jaetulla näytöllä."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Sovellus ei tue jaetun näytön tilaa."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tämän sovelluksen voi avata vain yhdessä ikkunassa."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Sovellus ei ehkä toimi toissijaisella näytöllä."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Sovellus ei tue käynnistämistä toissijaisilla näytöillä."</string> <string name="accessibility_divider" msgid="703810061635792791">"Näytön jakaja"</string> + <string name="divider_title" msgid="5482989479865361192">"Näytönjakaja"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vasen koko näytölle"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vasen 70 %"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasen 50 %"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Kupla"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Ylläpidä"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Kupla ohitettu."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Napauta, niin sovellus käynnistyy uudelleen ja siirtyy koko näytön tilaan."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Napauta, niin sovellus käynnistyy uudelleen paremmin näytölle sopivana."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Onko kameran kanssa ongelmia?\nKorjaa napauttamalla"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Eikö ongelma ratkennut?\nKumoa napauttamalla"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ei ongelmia kameran kanssa? Hylkää napauttamalla."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Osa sovelluksista toimii parhaiten pystytilassa"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Kokeile jotakin näistä vaihtoehdoista, jotta saat parhaan hyödyn näytön tilasta"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Käännä laitetta, niin se siirtyy koko näytön tilaan"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Kaksoisnapauta sovellusta, jos haluat siirtää sitä"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Näe ja tee enemmän"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Käytä jaettua näyttöä vetämällä tähän toinen sovellus"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kaksoisnapauta sovelluksen ulkopuolella, jos haluat siirtää sitä"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Pienennä"</string> + <string name="close_button_text" msgid="2913281996024033299">"Sulje"</string> + <string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string> + <string name="handle_text" msgid="1766582106752184456">"Kahva"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Koko näyttö"</string> + <string name="desktop_text" msgid="1077633567027630454">"Työpöytätila"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Jaettu näyttö"</string> + <string name="more_button_text" msgid="3655388105592893530">"Lisää"</string> + <string name="float_button_text" msgid="9221657008391364581">"Kelluva ikkuna"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml index 3e8bf9032780..f580d01691f9 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Kuva kuvassa"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nimetön)"</string> - <string name="pip_close" msgid="9135220303720555525">"Sulje PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Sulje"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Koko näyttö"</string> - <string name="pip_move" msgid="1544227837964635439">"Siirrä PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Laajenna PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Tiivistä PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Asetukset: paina "<annotation icon="home_icon">"ALOITUSNÄYTTÖPAINIKETTA"</annotation>" kahdesti"</string> + <string name="pip_move" msgid="158770205886688553">"Siirrä"</string> + <string name="pip_expand" msgid="1051966011679297308">"Laajenna"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Tiivistä"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Asetukset: paina "<annotation icon="home_icon">"ALOITUSNÄYTTÖPAINIKETTA"</annotation>" kahdesti"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Kuva kuvassa ‑valikko."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Siirrä vasemmalle"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Siirrä oikealle"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Siirrä ylös"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Siirrä alas"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Valmis"</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 62b2bb65a603..634880072d47 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Paramètres"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Entrer dans l\'écran partagé"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu d\'incrustation d\'image"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> est en mode d\'incrustation d\'image"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Si vous ne voulez pas que <xliff:g id="NAME">%s</xliff:g> utilise cette fonctionnalité, touchez l\'écran pour ouvrir les paramètres, puis désactivez-la."</string> <string name="pip_play" msgid="3496151081459417097">"Lire"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'application n\'est pas compatible avec l\'écran partagé."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette application ne peut être ouverte que dans une fenêtre."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string> <string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string> + <string name="divider_title" msgid="5482989479865361192">"Séparateur d\'écran partagé"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Plein écran à la gauche"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % à la gauche"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % à la gauche"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle ignorée."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Touchez pour redémarrer cette application et passer en plein écran."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Certaines applications fonctionnent mieux en mode portrait"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Essayez l\'une de ces options pour tirer le meilleur parti de votre espace"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Faites pivoter votre appareil pour passer en plein écran"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Touchez deux fois à côté d\'une application pour la repositionner"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et en faire plus"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Faites glisser une autre application pour utiliser l\'écran partagé"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une application pour la repositionner"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string> + <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string> + <string name="back_button_text" msgid="1469718707134137085">"Retour"</string> + <string name="handle_text" msgid="1766582106752184456">"Identifiant"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string> + <string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string> + <string name="more_button_text" msgid="3655388105592893530">"Plus"</string> + <string name="float_button_text" msgid="9221657008391364581">"Flottant"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml index 66e13b89c64b..39a785d4fcc0 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Incrustation d\'image"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Aucun programme de titre)"</string> - <string name="pip_close" msgid="9135220303720555525">"Fermer mode IDI"</string> + <string name="pip_close" msgid="2955969519031223530">"Fermer"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string> - <string name="pip_move" msgid="1544227837964635439">"Déplacer l\'image incrustée"</string> - <string name="pip_expand" msgid="7605396312689038178">"Développer l\'image incrustée"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Réduire l\'image incrustée"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Appuyez deux fois sur "<annotation icon="home_icon">" ACCUEIL "</annotation>" pour les commandes"</string> + <string name="pip_move" msgid="158770205886688553">"Déplacer"</string> + <string name="pip_expand" msgid="1051966011679297308">"Développer"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Réduire"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Appuyez deux fois sur "<annotation icon="home_icon">"ACCUEIL"</annotation>" pour les commandes"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu d\'incrustation d\'image."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Déplacer vers la gauche"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Déplacer vers la droite"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Déplacer vers le haut"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Déplacer vers le bas"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"OK"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index b3e22af0a3e3..184221345b23 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Paramètres"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accéder à l\'écran partagé"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu \"Picture-in-picture\""</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> est en mode Picture-in-picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Si vous ne voulez pas que l\'application <xliff:g id="NAME">%s</xliff:g> utilise cette fonctionnalité, appuyez ici pour ouvrir les paramètres et la désactiver."</string> <string name="pip_play" msgid="3496151081459417097">"Lecture"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Application incompatible avec l\'écran partagé."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette appli ne peut être ouverte que dans 1 fenêtre."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string> <string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string> + <string name="divider_title" msgid="5482989479865361192">"Séparateur d\'écran partagé"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Écran de gauche en plein écran"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Écran de gauche à 70 %"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Écran de gauche à 50 %"</string> @@ -63,7 +66,7 @@ <string name="bubble_dismiss_text" msgid="8816558050659478158">"Fermer la bulle"</string> <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher la conversation dans une bulle"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatter en utilisant des bulles"</string> - <string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes ou bulles. Appuyez sur la bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string> + <string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes ou de bulles. Appuyez sur la bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Contrôlez les bulles à tout moment"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Appuyez sur \"Gérer\" pour désactiver les bulles de cette application"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle fermée."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Appuyez pour redémarrer cette application et activer le mode plein écran."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Appuyez pour redémarrer cette appli et avoir une meilleure vue."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo ?\nAppuyez pour réajuster"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu ?\nAppuyez pour rétablir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo ? Appuyez pour ignorer."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Certaines applis fonctionnent mieux en mode Portrait"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Essayez l\'une de ces options pour exploiter pleinement l\'espace"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Faites pivoter l\'appareil pour passer en plein écran"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Appuyez deux fois à côté d\'une appli pour la repositionner"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et interagir plus"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Appuyez deux fois en dehors d\'une appli pour la repositionner"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string> + <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string> + <string name="back_button_text" msgid="1469718707134137085">"Retour"</string> + <string name="handle_text" msgid="1766582106752184456">"Poignée"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string> + <string name="desktop_text" msgid="1077633567027630454">"Mode ordinateur"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string> + <string name="more_button_text" msgid="3655388105592893530">"Plus"</string> + <string name="float_button_text" msgid="9221657008391364581">"Flottante"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml index ed9baf5b6215..db4bc54cf665 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programme sans titre)"</string> - <string name="pip_close" msgid="9135220303720555525">"Fermer mode PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Fermer"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string> - <string name="pip_move" msgid="1544227837964635439">"Déplacer le PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Développer la fenêtre PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Réduire la fenêtre PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Menu de commandes : appuyez deux fois sur "<annotation icon="home_icon">"ACCUEIL"</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Déplacer"</string> + <string name="pip_expand" msgid="1051966011679297308">"Développer"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Réduire"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Commandes : appuyez deux fois sur "<annotation icon="home_icon">"ACCUEIL"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu \"Picture-in-picture\"."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Déplacer vers la gauche"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Déplacer vers la droite"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Déplacer vers le haut"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Déplacer vers le bas"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"OK"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index b8e039602243..2e05d4c3b548 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Configuración"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Inserir pantalla dividida"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú de pantalla superposta"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está na pantalla superposta"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Se non queres que <xliff:g id="NAME">%s</xliff:g> utilice esta función, toca a configuración para abrir as opcións e desactivar a función."</string> <string name="pip_play" msgid="3496151081459417097">"Reproducir"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Non esconder"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Pode que a aplicación non funcione coa pantalla dividida."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A aplicación non é compatible coa función de pantalla dividida."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación só se pode abrir en 1 ventá."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É posible que a aplicación non funcione nunha pantalla secundaria."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A aplicación non se pode iniciar en pantallas secundarias."</string> <string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string> + <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla completa á esquerda"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % á esquerda"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % á esquerda"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Burbulla"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Xestionar"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ignorouse a burbulla."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Toca o botón para reiniciar esta aplicación e abrila en pantalla completa."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Toca o botón para reiniciar esta aplicación e gozar dunha mellor visualización."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tes problemas coa cámara?\nToca para reaxustala"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Non se solucionaron os problemas?\nToca para reverter o seu tratamento"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Non hai problemas coa cámara? Tocar para ignorar."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Algunhas aplicacións funcionan mellor en modo vertical"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Proba unha destas opcións para sacar o máximo proveito do espazo da pantalla"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Xira o dispositivo para ver o contido en pantalla completa"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toca dúas veces a carón dunha aplicación para cambiala de posición"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ver e facer máis"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra outra aplicación para usar a pantalla dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dúas veces fóra da aplicación para cambiala de posición"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> + <string name="close_button_text" msgid="2913281996024033299">"Pechar"</string> + <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string> + <string name="handle_text" msgid="1766582106752184456">"Controlador"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string> + <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string> + <string name="more_button_text" msgid="3655388105592893530">"Máis"</string> + <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml index a057434d7853..22e68d3707ac 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pantalla superposta"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sen título)"</string> - <string name="pip_close" msgid="9135220303720555525">"Pechar PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Pechar"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> - <string name="pip_move" msgid="1544227837964635439">"Mover pantalla superposta"</string> - <string name="pip_expand" msgid="7605396312689038178">"Despregar pantalla superposta"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Contraer pantalla superposta"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Preme "<annotation icon="home_icon">"INICIO"</annotation>" dúas veces para acceder aos controis"</string> + <string name="pip_move" msgid="158770205886688553">"Mover"</string> + <string name="pip_expand" msgid="1051966011679297308">"Despregar"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Contraer"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Preme "<annotation icon="home_icon">"INICIO"</annotation>" dúas veces para acceder aos controis"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de pantalla superposta."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover cara á esquerda"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover cara á dereita"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover cara arriba"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover cara abaixo"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Feito"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index deda2d755e20..e680298c90bc 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"સેટિંગ"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"વિભાજિત સ્ક્રીન મોડમાં દાખલ થાઓ"</string> <string name="pip_menu_title" msgid="5393619322111827096">"મેનૂ"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ચિત્રમાં ચિત્ર મેનૂ"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ચિત્રમાં-ચિત્રની અંદર છે"</string> <string name="pip_notification_message" msgid="8854051911700302620">"જો તમે નથી ઇચ્છતા કે <xliff:g id="NAME">%s</xliff:g> આ સુવિધાનો ઉપયોગ કરે, તો સેટિંગ ખોલવા માટે ટૅપ કરો અને તેને બંધ કરો."</string> <string name="pip_play" msgid="3496151081459417097">"ચલાવો"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"વિભાજિત-સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ઍપ્લિકેશન સ્ક્રીન-વિભાજનનું સમર્થન કરતી નથી."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string> <string name="accessibility_divider" msgid="703810061635792791">"સ્પ્લિટ-સ્ક્રીન વિભાજક"</string> + <string name="divider_title" msgid="5482989479865361192">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ડાબી પૂર્ણ સ્ક્રીન"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ડાબે 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ડાબે 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"બબલ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"મેનેજ કરો"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"બબલ છોડી દેવાયો."</string> - <string name="restart_button_description" msgid="5887656107651190519">"આ ઍપ ફરીથી ચાલુ કરવા માટે ટૅપ કરીને પૂર્ણ સ્ક્રીન કરો."</string> + <string name="restart_button_description" msgid="6712141648865547958">"વધુ સારા વ્યૂ માટે, આ ઍપને ફરી શરૂ કરવા ટૅપ કરો."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"અમુક ઍપ પોર્ટ્રેટ મોડમાં શ્રેષ્ઠ રીતે કાર્ય કરે છે"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"તમારી સ્પેસનો વધુને વધુ લાભ લેવા માટે, આ વિકલ્પોમાંથી કોઈ એક અજમાવો"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"પૂર્ણ સ્ક્રીન મોડ લાગુ કરવા માટે, તમારા ડિવાઇસને ફેરવો"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બાજુમાં બે વાર ટૅપ કરો"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"જુઓ અને બીજું ઘણું કરો"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"સ્ક્રીન વિભાજન માટે કોઈ અન્ય ઍપમાં ખેંચો"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બહાર બે વાર ટૅપ કરો"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"સમજાઈ ગયું"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"વધુ માહિતી માટે મોટું કરો."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string> + <string name="minimize_button_text" msgid="271592547935841753">"નાનું કરો"</string> + <string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string> + <string name="back_button_text" msgid="1469718707134137085">"પાછળ"</string> + <string name="handle_text" msgid="1766582106752184456">"હૅન્ડલ"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"પૂર્ણસ્ક્રીન"</string> + <string name="desktop_text" msgid="1077633567027630454">"ડેસ્કટૉપ મોડ"</string> + <string name="split_screen_text" msgid="1396336058129570886">"સ્ક્રીનને વિભાજિત કરો"</string> + <string name="more_button_text" msgid="3655388105592893530">"વધુ"</string> + <string name="float_button_text" msgid="9221657008391364581">"ફ્લોટિંગ વિન્ડો"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml index d9525910e4c6..01b9b4b987d0 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ચિત્રમાં-ચિત્ર"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(કોઈ ટાઇટલ પ્રોગ્રામ નથી)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP બંધ કરો"</string> + <string name="pip_close" msgid="2955969519031223530">"બંધ કરો"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"પૂર્ણ સ્ક્રીન"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP ખસેડો"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP મોટી કરો"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP નાની કરો"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" નિયંત્રણો માટે "<annotation icon="home_icon">" હોમ "</annotation>" બટન પર બે વાર દબાવો"</string> + <string name="pip_move" msgid="158770205886688553">"ખસેડો"</string> + <string name="pip_expand" msgid="1051966011679297308">"મોટું કરો"</string> + <string name="pip_collapse" msgid="3903295106641385962">"નાનું કરો"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"નિયંત્રણો માટે "<annotation icon="home_icon">"હોમ"</annotation>" બટન બે વાર દબાવો"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ચિત્રમાં ચિત્ર મેનૂ."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ડાબે ખસેડો"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"જમણે ખસેડો"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ઉપર ખસેડો"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"નીચે ખસેડો"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"થઈ ગયું"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index 36b11514c7e5..9a926d88f7b8 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"सेटिंग"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रीन मोड में जाएं"</string> <string name="pip_menu_title" msgid="5393619322111827096">"मेन्यू"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"पिक्चर में पिक्चर मेन्यू"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"पिक्चर में पिक्चर\" के अंदर है"</string> <string name="pip_notification_message" msgid="8854051911700302620">"अगर आप नहीं चाहते कि <xliff:g id="NAME">%s</xliff:g> इस सुविधा का उपयोग करे, तो सेटिंग खोलने के लिए टैप करें और उसे बंद करें ."</string> <string name="pip_play" msgid="3496151081459417097">"चलाएं"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"दिखाएं"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"ऐप्लिकेशन शायद स्प्लिट स्क्रीन मोड में काम न करे."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ऐप विभाजित स्क्रीन का समर्थन नहीं करता है."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."</string> <string name="accessibility_divider" msgid="703810061635792791">"विभाजित स्क्रीन विभाजक"</string> + <string name="divider_title" msgid="5482989479865361192">"स्प्लिट स्क्रीन डिवाइडर"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"बाईं स्क्रीन को फ़ुल स्क्रीन बनाएं"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"बाईं स्क्रीन को 70% बनाएं"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"बाईं स्क्रीन को 50% बनाएं"</string> @@ -65,20 +68,30 @@ <string name="bubbles_user_education_title" msgid="2112319053732691899">"बबल्स का इस्तेमाल करके चैट करें"</string> <string name="bubbles_user_education_description" msgid="4215862563054175407">"नई बातचीत फ़्लोटिंग आइकॉन या बबल्स की तरह दिखेंगी. बबल को खोलने के लिए टैप करें. इसे एक जगह से दूसरी जगह ले जाने के लिए खींचें और छोड़ें."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"जब चाहें, बबल्स को कंट्रोल करें"</string> - <string name="bubbles_user_education_manage" msgid="3460756219946517198">"इस ऐप्लिकेशन पर बबल्स को बंद करने के लिए \'प्रबंधित करें\' पर टैप करें"</string> + <string name="bubbles_user_education_manage" msgid="3460756219946517198">"इस ऐप्लिकेशन पर बबल्स को बंद करने के लिए \'मैनेज करें\' पर टैप करें"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ठीक है"</string> - <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हाल ही के बबल्स मौजूद नहीं हैं"</string> + <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हाल ही के कोई बबल्स नहीं हैं"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हाल ही के बबल्स और हटाए गए बबल्स यहां दिखेंगे"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल खारिज किया गया."</string> - <string name="restart_button_description" msgid="5887656107651190519">"इस ऐप्लिकेशन को रीस्टार्ट करने और फ़ुल स्क्रीन पर देखने के लिए टैप करें."</string> + <string name="restart_button_description" msgid="6712141648865547958">"टैप करके ऐप्लिकेशन को रीस्टार्ट करें और बेहतर व्यू पाएं."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"कुछ ऐप्लिकेशन, पोर्ट्रेट मोड में सबसे अच्छी तरह काम करते हैं"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"जगह का पूरा इस्तेमाल करने के लिए, इनमें से किसी एक विकल्प को आज़माएं"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"फ़ुल स्क्रीन मोड में जाने के लिए, डिवाइस को घुमाएं"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बगल में दो बार टैप करें"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पूरी जानकारी लेकर, बेहतर तरीके से काम करें"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रीन के लिए, दूसरे ऐप्लिकेशन को खींचें और छोड़ें"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बाहर दो बार टैप करें"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string> + <string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string> + <string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string> + <string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string> + <string name="handle_text" msgid="1766582106752184456">"हैंडल"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"फ़ुलस्क्रीन"</string> + <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string> + <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन मोड"</string> + <string name="more_button_text" msgid="3655388105592893530">"ज़्यादा देखें"</string> + <string name="float_button_text" msgid="9221657008391364581">"फ़्लोट"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml index d897ac73f80d..595435bcf8b8 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"पिक्चर में पिक्चर"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई शीर्षक कार्यक्रम नहीं)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP बंद करें"</string> + <string name="pip_close" msgid="2955969519031223530">"बंद करें"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"फ़ुल स्क्रीन"</string> - <string name="pip_move" msgid="1544227837964635439">"पीआईपी को दूसरी जगह लेकर जाएं"</string> - <string name="pip_expand" msgid="7605396312689038178">"पीआईपी विंडो को बड़ा करें"</string> - <string name="pip_collapse" msgid="5732233773786896094">"पीआईपी विंडो को छोटा करें"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" कंट्रोल मेन्यू पर जाने के लिए, "<annotation icon="home_icon">" होम बटन"</annotation>" दो बार दबाएं"</string> + <string name="pip_move" msgid="158770205886688553">"ले जाएं"</string> + <string name="pip_expand" msgid="1051966011679297308">"बड़ा करें"</string> + <string name="pip_collapse" msgid="3903295106641385962">"छोटा करें"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"कंट्रोल मेन्यू पर जाने के लिए "<annotation icon="home_icon">" होम"</annotation>" को दो बार दबाएं"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"पिक्चर में पिक्चर मेन्यू."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"बाईं ओर ले जाएं"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"दाईं ओर ले जाएं"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ऊपर ले जाएं"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"नीचे ले जाएं"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"हो गया"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index 5ecc5585a6e9..23a5970d805e 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Postavke"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Otvorite podijeljeni zaslon"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Izbornik"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Izbornik slike u slici"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> jest na slici u slici"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ako ne želite da aplikacija <xliff:g id="NAME">%s</xliff:g> upotrebljava tu značajku, dodirnite da biste otvorili postavke i isključili je."</string> <string name="pip_play" msgid="3496151081459417097">"Reproduciraj"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poništite sakrivanje"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podijeljeni zaslon."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova se aplikacija može otvoriti samo u jednom prozoru."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim zaslonima."</string> <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog zaslona"</string> + <string name="divider_title" msgid="5482989479865361192">"Razdjelnik podijeljenog zaslona"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lijevi zaslon u cijeli zaslon"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Lijevi zaslon na 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevi zaslon na 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić odbačen."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da biste ponovo pokrenuli tu aplikaciju i prikazali je na cijelom zaslonu."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s fotoaparatom?\nDodirnite za popravak"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije riješen?\nDodirnite za vraćanje"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema s fotoaparatom? Dodirnite za odbacivanje."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Neke aplikacije najbolje funkcioniraju u portretnom usmjerenju"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Isprobajte jednu od ovih opcija da biste maksimalno iskoristili prostor"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zakrenite uređaj radi prikaza na cijelom zaslonu"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da biste joj promijenili položaj"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Gledajte i učinite više"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Povucite drugu aplikaciju unutra da biste podijelili zaslon"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste je premjestili"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite da biste saznali više."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimiziraj"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string> + <string name="back_button_text" msgid="1469718707134137085">"Natrag"</string> + <string name="handle_text" msgid="1766582106752184456">"Pokazivač"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Puni zaslon"</string> + <string name="desktop_text" msgid="1077633567027630454">"Stolni način rada"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Razdvojeni zaslon"</string> + <string name="more_button_text" msgid="3655388105592893530">"Više"</string> + <string name="float_button_text" msgid="9221657008391364581">"Plutajući"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml index 8f5f3164c4d7..965b9b8c31c6 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Slika u slici"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string> - <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Zatvori"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli zaslon"</string> - <string name="pip_move" msgid="1544227837964635439">"Premjesti PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Proširi PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Sažmi PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Dvaput pritisnite "<annotation icon="home_icon">"POČETNI ZASLON"</annotation>" za kontrole"</string> + <string name="pip_move" msgid="158770205886688553">"Premjesti"</string> + <string name="pip_expand" msgid="1051966011679297308">"Proširi"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Sažmi"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Dvaput pritisnite "<annotation icon="home_icon">"POČETNI ZASLON"</annotation>" za kontrole"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Izbornik slike u slici."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Pomaknite ulijevo"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Pomaknite udesno"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Pomaknite prema gore"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pomaknite prema dolje"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gotovo"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index 2295250e2853..1bbbdb7786b4 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Beállítások"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Váltás osztott képernyőre"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Kép a képben menü"</string> <string name="pip_notification_title" msgid="1347104727641353453">"A(z) <xliff:g id="NAME">%s</xliff:g> kép a képben funkciót használ"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ha nem szeretné, hogy a(z) <xliff:g id="NAME">%s</xliff:g> használja ezt a funkciót, koppintson a beállítások megnyitásához, és kapcsolja ki."</string> <string name="pip_play" msgid="3496151081459417097">"Lejátszás"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Félretevés megszüntetése"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Lehet, hogy az alkalmazás nem működik osztott képernyős nézetben."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Az alkalmazás nem támogatja az osztott képernyős nézetet."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ez az alkalmazás csak egy ablakban nyitható meg."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Az alkalmazást nem lehet másodlagos kijelzőn elindítani."</string> <string name="accessibility_divider" msgid="703810061635792791">"Elválasztó az osztott nézetben"</string> + <string name="divider_title" msgid="5482989479865361192">"Elválasztó az osztott nézetben"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Bal oldali teljes képernyőre"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Bal oldali 70%-ra"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Bal oldali 50%-ra"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Buborék"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Kezelés"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Buborék elvetve."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Koppintson az alkalmazás újraindításához és a teljes képernyős mód elindításához."</string> + <string name="restart_button_description" msgid="6712141648865547958">"A jobb nézet érdekében koppintson az alkalmazás újraindításához."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerával kapcsolatos problémába ütközött?\nKoppintson a megoldáshoz."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nem sikerült a hiba kijavítása?\nKoppintson a visszaállításhoz."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nincsenek problémái kamerával? Koppintson az elvetéshez."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Egyes alkalmazások álló tájolásban működnek a leghatékonyabban"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Próbálja ki az alábbi beállítások egyikét, hogy a legjobban ki tudja használni képernyő területét"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"A teljes képernyős mód elindításához forgassa el az eszközt"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Koppintson duplán az alkalmazás mellett az áthelyezéséhez"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Több mindent láthat és tehet"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Húzzon ide egy másik alkalmazást az osztott képernyő használatához"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Koppintson duplán az alkalmazáson kívül az áthelyezéséhez"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kibontással további információkhoz juthat."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Kis méret"</string> + <string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string> + <string name="back_button_text" msgid="1469718707134137085">"Vissza"</string> + <string name="handle_text" msgid="1766582106752184456">"Fogópont"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Teljes képernyő"</string> + <string name="desktop_text" msgid="1077633567027630454">"Asztali üzemmód"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Osztott képernyő"</string> + <string name="more_button_text" msgid="3655388105592893530">"Továbbiak"</string> + <string name="float_button_text" msgid="9221657008391364581">"Lebegő"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml index fc8d79589121..90cbfe643c82 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Kép a képben"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Cím nélküli program)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP bezárása"</string> + <string name="pip_close" msgid="2955969519031223530">"Bezárás"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Teljes képernyő"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP áthelyezése"</string> - <string name="pip_expand" msgid="7605396312689038178">"Kép a képben kibontása"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Kép a képben összecsukása"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Vezérlők: "<annotation icon="home_icon">" KEZDŐKÉPERNYŐ "</annotation>" gomb kétszer megnyomva"</string> + <string name="pip_move" msgid="158770205886688553">"Áthelyezés"</string> + <string name="pip_expand" msgid="1051966011679297308">"Kibontás"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Összecsukás"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Vezérlők: A "<annotation icon="home_icon">"KEZDŐKÉPERNYŐ"</annotation>" gomb kétszeri megnyomása"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Kép a képben menü."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mozgatás balra"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mozgatás jobbra"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mozgatás felfelé"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mozgatás lefelé"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Kész"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 208936539094..6eef4afda3ff 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Կարգավորումներ"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Մտնել տրոհված էկրանի ռեժիմ"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Ընտրացանկ"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"«Նկար նկարի մեջ» ռեժիմի ընտրացանկ"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>-ը «Նկար նկարի մեջ» ռեժիմում է"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Եթե չեք ցանկանում, որ <xliff:g id="NAME">%s</xliff:g>-ն օգտագործի այս գործառույթը, հպեք՝ կարգավորումները բացելու և այն անջատելու համար։"</string> <string name="pip_play" msgid="3496151081459417097">"Նվագարկել"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ցուցադրել"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում։"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Հավելվածը չի աջակցում էկրանի տրոհումը:"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում։"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Հավելվածը չի աջակցում գործարկումը լրացուցիչ էկրանների վրա"</string> <string name="accessibility_divider" msgid="703810061635792791">"Տրոհված էկրանի բաժանիչ"</string> + <string name="divider_title" msgid="5482989479865361192">"Տրոհված էկրանի բաժանիչ"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ձախ էկրանը՝ լիաէկրան"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ձախ էկրանը՝ 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ձախ էկրանը՝ 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Պղպջակ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Կառավարել"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ամպիկը փակվեց։"</string> - <string name="restart_button_description" msgid="5887656107651190519">"Հպեք՝ հավելվածը վերագործարկելու և լիաէկրան ռեժիմին անցնելու համար։"</string> + <string name="restart_button_description" msgid="6712141648865547958">"Հպեք՝ հավելվածը վերագործարկելու և ավելի հարմար տեսք ընտրելու համար։"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Որոշ հավելվածներ լավագույնս աշխատում են դիմանկարի ռեժիմում"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Փորձեք այս տարբերակներից մեկը՝ տարածքը հնարավորինս արդյունավետ օգտագործելու համար"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Պտտեք սարքը՝ լիաէկրան ռեժիմին անցնելու համար"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Միաժամանակ կատարեք մի քանի առաջադրանք"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Եղավ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ծավալեք՝ ավելին իմանալու համար։"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string> + <string name="close_button_text" msgid="2913281996024033299">"Փակել"</string> + <string name="back_button_text" msgid="1469718707134137085">"Հետ"</string> + <string name="handle_text" msgid="1766582106752184456">"Նշիչ"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Լիաէկրան"</string> + <string name="desktop_text" msgid="1077633567027630454">"Համակարգչի ռեժիմ"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Տրոհված էկրան"</string> + <string name="more_button_text" msgid="3655388105592893530">"Ավելին"</string> + <string name="float_button_text" msgid="9221657008391364581">"Լողացող պատուհան"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml index f5665b8dd166..30b5911147b5 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Նկար նկարի մեջ"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Առանց վերնագրի ծրագիր)"</string> - <string name="pip_close" msgid="9135220303720555525">"Փակել PIP-ն"</string> + <string name="pip_close" msgid="2955969519031223530">"Փակել"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Լիէկրան"</string> - <string name="pip_move" msgid="1544227837964635439">"Տեղափոխել PIP-ը"</string> - <string name="pip_expand" msgid="7605396312689038178">"Ծավալել PIP-ը"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Ծալել PIP-ը"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Կարգավորումների համար կրկնակի սեղմեք "<annotation icon="home_icon">"ԳԼԽԱՎՈՐ ԷԿՐԱՆ"</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Տեղափոխել"</string> + <string name="pip_expand" msgid="1051966011679297308">"Ծավալել"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Ծալել"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Կարգավորումների համար կրկնակի սեղմեք "<annotation icon="home_icon">"ԳԼԽԱՎՈՐ ԷԿՐԱՆ"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"«Նկար նկարի մեջ» ռեժիմի ընտրացանկ։"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Տեղափոխել ձախ"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Տեղափոխել աջ"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Տեղափոխել վերև"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Տեղափոխել ներքև"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Պատրաստ է"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index 1b46b2fe2570..61a9558972e0 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Setelan"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Masuk ke mode layar terpisah"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu Picture-in-Picture"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> adalah picture-in-picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Jika Anda tidak ingin <xliff:g id="NAME">%s</xliff:g> menggunakan fitur ini, ketuk untuk membuka setelan dan menonaktifkannya."</string> <string name="pip_play" msgid="3496151081459417097">"Putar"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Batalkan stash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikasi mungkin tidak berfungsi dengan layar terpisah."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App tidak mendukung layar terpisah."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplikasi ini hanya dapat dibuka di 1 jendela."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikasi mungkin tidak berfungsi pada layar sekunder."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikasi tidak mendukung peluncuran pada layar sekunder."</string> <string name="accessibility_divider" msgid="703810061635792791">"Pembagi layar terpisah"</string> + <string name="divider_title" msgid="5482989479865361192">"Pembagi layar terpisah"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Layar penuh di kiri"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kiri 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Kelola"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon ditutup."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Ketuk untuk memulai ulang aplikasi ini dan membuka layar penuh."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Ketuk untuk memulai ulang aplikasi ini agar mendapatkan tampilan yang lebih baik."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Masalah kamera?\nKetuk untuk memperbaiki"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tidak dapat diperbaiki?\nKetuk untuk mengembalikan"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tidak ada masalah kamera? Ketuk untuk menutup."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Beberapa aplikasi berfungsi paling baik dalam mode potret"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Coba salah satu opsi berikut untuk mengoptimalkan area layar Anda"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Putar perangkat untuk tampilan layar penuh"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ketuk dua kali di samping aplikasi untuk mengubah posisinya"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih banyak hal"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Tarik aplikasi lain untuk menggunakan layar terpisah"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimalkan"</string> + <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string> + <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string> + <string name="handle_text" msgid="1766582106752184456">"Tuas"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Layar Penuh"</string> + <string name="desktop_text" msgid="1077633567027630454">"Mode Desktop"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Layar Terpisah"</string> + <string name="more_button_text" msgid="3655388105592893530">"Lainnya"</string> + <string name="float_button_text" msgid="9221657008391364581">"Mengambang"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-in/strings_tv.xml b/libs/WindowManager/Shell/res/values-in/strings_tv.xml index a1535653f679..0fda69f6c0e4 100644 --- a/libs/WindowManager/Shell/res/values-in/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-in/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tanpa judul)"</string> - <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Tutup"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Layar penuh"</string> - <string name="pip_move" msgid="1544227837964635439">"Pindahkan PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Luaskan PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Ciutkan PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Tekan dua kali "<annotation icon="home_icon">" HOME "</annotation>" untuk membuka kontrol"</string> + <string name="pip_move" msgid="158770205886688553">"Pindahkan"</string> + <string name="pip_expand" msgid="1051966011679297308">"Luaskan"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Ciutkan"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Tekan dua kali "<annotation icon="home_icon">"HOME"</annotation>" untuk membuka kontrol"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu Picture-in-Picture."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Pindahkan ke kiri"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Pindahkan ke kanan"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Pindahkan ke atas"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pindahkan ke bawah"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Selesai"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index a201c95137f3..0b873bc82e63 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Stillingar"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Opna skjáskiptingu"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Valmynd"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Valmynd fyrir mynd í mynd"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> er með mynd í mynd"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ef þú vilt ekki að <xliff:g id="NAME">%s</xliff:g> noti þennan eiginleika skaltu ýta til að opna stillingarnar og slökkva á því."</string> <string name="pip_play" msgid="3496151081459417097">"Spila"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Taka úr geymslu"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Hugsanlega virkar forritið ekki með skjáskiptingu."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Forritið styður ekki að skjánum sé skipt."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aðeins er hægt að opna þetta forrit í 1 glugga."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Hugsanlegt er að forritið virki ekki á öðrum skjá."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Forrit styður ekki opnun á öðrum skjá."</string> <string name="accessibility_divider" msgid="703810061635792791">"Skjáskipting"</string> + <string name="divider_title" msgid="5482989479865361192">"Skjáskipting"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vinstri á öllum skjánum"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vinstri 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vinstri 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Blaðra"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Stjórna"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Blöðru lokað."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Ýttu til að endurræsa forritið og sýna það á öllum skjánum."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Ýta til að endurræsa forritið og fá betri sýn."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Myndavélavesen?\nÝttu til að breyta stærð"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ennþá vesen?\nÝttu til að afturkalla"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ekkert myndavélavesen? Ýttu til að hunsa."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Sum forrit virka best í skammsniði"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prófaðu einhvern af eftirfarandi valkostum til að nýta plássið sem best"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Snúðu tækinu til að nota allan skjáinn"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ýttu tvisvar við hlið forritsins til að færa það"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sjáðu og gerðu meira"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dragðu annað forrit inn til að nota skjáskiptingu"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ýttu tvisvar utan við forrit til að færa það"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minnka"</string> + <string name="close_button_text" msgid="2913281996024033299">"Loka"</string> + <string name="back_button_text" msgid="1469718707134137085">"Til baka"</string> + <string name="handle_text" msgid="1766582106752184456">"Handfang"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Allur skjárinn"</string> + <string name="desktop_text" msgid="1077633567027630454">"Skjáborðsstilling"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Skjáskipting"</string> + <string name="more_button_text" msgid="3655388105592893530">"Meira"</string> + <string name="float_button_text" msgid="9221657008391364581">"Reikult"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-is/strings_tv.xml b/libs/WindowManager/Shell/res/values-is/strings_tv.xml index 70ca1afe3aea..e0d604f30d1a 100644 --- a/libs/WindowManager/Shell/res/values-is/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-is/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Mynd í mynd"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Efni án titils)"</string> - <string name="pip_close" msgid="9135220303720555525">"Loka mynd í mynd"</string> + <string name="pip_close" msgid="2955969519031223530">"Loka"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Allur skjárinn"</string> - <string name="pip_move" msgid="1544227837964635439">"Færa innfellda mynd"</string> - <string name="pip_expand" msgid="7605396312689038178">"Stækka innfellda mynd"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Minnka innfellda mynd"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Ýttu tvisvar á "<annotation icon="home_icon">" HEIM "</annotation>" til að opna stillingar"</string> + <string name="pip_move" msgid="158770205886688553">"Færa"</string> + <string name="pip_expand" msgid="1051966011679297308">"Stækka"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Minnka"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Ýttu tvisvar á "<annotation icon="home_icon">"HEIM"</annotation>" til að opna stillingar"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Valmynd fyrir mynd í mynd."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Færa til vinstri"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Færa til hægri"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Færa upp"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Færa niður"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Lokið"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index 7157ed088d30..da4d0bbe1951 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Impostazioni"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accedi a schermo diviso"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu Picture in picture"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> è in Picture in picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Se non desideri che l\'app <xliff:g id="NAME">%s</xliff:g> utilizzi questa funzione, tocca per aprire le impostazioni e disattivarla."</string> <string name="pip_play" msgid="3496151081459417097">"Riproduci"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Annulla accantonamento"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"L\'app potrebbe non funzionare con lo schermo diviso."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'app non supporta la modalità Schermo diviso."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Questa app può essere aperta soltanto in 1 finestra."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"L\'app potrebbe non funzionare su un display secondario."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'app non supporta l\'avvio su display secondari."</string> <string name="accessibility_divider" msgid="703810061635792791">"Strumento per schermo diviso"</string> + <string name="divider_title" msgid="5482989479865361192">"Strumento per schermo diviso"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Schermata sinistra a schermo intero"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Schermata sinistra al 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Schermata sinistra al 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Fumetto"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestisci"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Fumetto ignorato."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tocca per riavviare l\'app e passare alla modalità a schermo intero."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tocca per riavviare quest\'app per una migliore visualizzazione."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi con la fotocamera?\nTocca per risolverli"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Il problema non si è risolto?\nTocca per ripristinare"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nessun problema con la fotocamera? Tocca per ignorare."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Alcune app funzionano in modo ottimale in verticale"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prova una di queste opzioni per ottimizzare lo spazio a tua disposizione"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ruota il dispositivo per passare alla modalità a schermo intero"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tocca due volte accanto a un\'app per riposizionarla"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Visualizza più contenuti e fai di più"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Trascina in un\'altra app per usare lo schermo diviso"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tocca due volte fuori da un\'app per riposizionarla"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Ingrandisci"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Riduci a icona"</string> + <string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string> + <string name="back_button_text" msgid="1469718707134137085">"Indietro"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Schermo intero"</string> + <string name="desktop_text" msgid="1077633567027630454">"Modalità desktop"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Schermo diviso"</string> + <string name="more_button_text" msgid="3655388105592893530">"Altro"</string> + <string name="float_button_text" msgid="9221657008391364581">"Mobile"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-it/strings_tv.xml b/libs/WindowManager/Shell/res/values-it/strings_tv.xml index cda627517872..267f67463917 100644 --- a/libs/WindowManager/Shell/res/values-it/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-it/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture in picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma senza titolo)"</string> - <string name="pip_close" msgid="9135220303720555525">"Chiudi PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Chiudi"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Schermo intero"</string> - <string name="pip_move" msgid="1544227837964635439">"Sposta PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Espandi PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Comprimi PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Premi due volte "<annotation icon="home_icon">" HOME "</annotation>" per aprire i controlli"</string> + <string name="pip_move" msgid="158770205886688553">"Sposta"</string> + <string name="pip_expand" msgid="1051966011679297308">"Espandi"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Comprimi"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Premi due volte "<annotation icon="home_icon">"HOME"</annotation>" per accedere ai controlli"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu Picture in picture."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Sposta a sinistra"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Sposta a destra"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Sposta su"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Sposta giù"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Fine"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index 52a6b0676222..e9a53ddcd1e6 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"הגדרות"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"כניסה למסך המפוצל"</string> <string name="pip_menu_title" msgid="5393619322111827096">"תפריט"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"תפריט \'תמונה בתוך תמונה\'"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> במצב תמונה בתוך תמונה"</string> <string name="pip_notification_message" msgid="8854051911700302620">"אם אינך רוצה שהתכונה הזו תשמש את <xliff:g id="NAME">%s</xliff:g>, יש להקיש כדי לפתוח את ההגדרות ולהשבית את התכונה."</string> <string name="pip_play" msgid="3496151081459417097">"הפעלה"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ביטול ההסתרה הזמנית"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"ייתכן שהאפליקציה לא תפעל במסך מפוצל."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"האפליקציה אינה תומכת במסך מפוצל."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ייתכן שהאפליקציה לא תפעל במסך משני."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"האפליקציה אינה תומכת בהפעלה במסכים משניים."</string> <string name="accessibility_divider" msgid="703810061635792791">"מחלק מסך מפוצל"</string> + <string name="divider_title" msgid="5482989479865361192">"מחלק מסך מפוצל"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"מסך שמאלי מלא"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"שמאלה 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"שמאלה 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"בועה"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ניהול"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"הבועה נסגרה."</string> - <string name="restart_button_description" msgid="5887656107651190519">"צריך להקיש כדי להפעיל מחדש את האפליקציה הזו ולעבור למסך מלא."</string> + <string name="restart_button_description" msgid="6712141648865547958">"כדי לראות טוב יותר יש להקיש ולהפעיל את האפליקציה הזו מחדש."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"חלק מהאפליקציות פועלות בצורה הטובה ביותר במצב תצוגה לאורך"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"כדי להפיק את המרב משטח המסך, ניתן לנסות את אחת מהאפשרויות האלה"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"מסובבים את המכשיר כדי לעבור לתצוגה במסך מלא"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"מקישים הקשה כפולה ליד אפליקציה כדי למקם אותה מחדש"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"רוצה לראות ולעשות יותר?"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך מפוצל"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string> + <string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string> + <string name="close_button_text" msgid="2913281996024033299">"סגירה"</string> + <string name="back_button_text" msgid="1469718707134137085">"חזרה"</string> + <string name="handle_text" msgid="1766582106752184456">"נקודת אחיזה"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"מסך מלא"</string> + <string name="desktop_text" msgid="1077633567027630454">"ממשק המחשב"</string> + <string name="split_screen_text" msgid="1396336058129570886">"מסך מפוצל"</string> + <string name="more_button_text" msgid="3655388105592893530">"עוד"</string> + <string name="float_button_text" msgid="9221657008391364581">"בלונים"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml index 30ce97b998ca..6b30f5642ad3 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"תמונה בתוך תמונה"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(תוכנית ללא כותרת)"</string> - <string name="pip_close" msgid="9135220303720555525">"סגירת PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"סגירה"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"מסך מלא"</string> - <string name="pip_move" msgid="1544227837964635439">"העברת תמונה בתוך תמונה (PIP)"</string> - <string name="pip_expand" msgid="7605396312689038178">"הרחבת חלון תמונה-בתוך-תמונה"</string> - <string name="pip_collapse" msgid="5732233773786896094">"כיווץ של חלון תמונה-בתוך-תמונה"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" לחיצה כפולה על "<annotation icon="home_icon">" הלחצן הראשי "</annotation>" תציג את אמצעי הבקרה"</string> + <string name="pip_move" msgid="158770205886688553">"העברה"</string> + <string name="pip_expand" msgid="1051966011679297308">"הרחבה"</string> + <string name="pip_collapse" msgid="3903295106641385962">"כיווץ"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"לחיצה כפולה על "<annotation icon="home_icon">"בית"</annotation>" תציג את אמצעי הבקרה"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"תפריט \'תמונה בתוך תמונה\'."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"הזזה שמאלה"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"הזזה ימינה"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"הזזה למעלה"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"הזזה למטה"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"סיום"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index 5a25c24ba034..2930cc3747b9 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"分割画面に切り替え"</string> <string name="pip_menu_title" msgid="5393619322111827096">"メニュー"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ピクチャー イン ピクチャーのメニュー"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>はピクチャー イン ピクチャーで表示中です"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g>でこの機能を使用しない場合は、タップして設定を開いて OFF にしてください。"</string> <string name="pip_play" msgid="3496151081459417097">"再生"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"表示"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"アプリは分割画面では動作しないことがあります。"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"アプリで分割画面がサポートされていません。"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"このアプリはウィンドウが 1 つの場合のみ開くことができます。"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"アプリはセカンダリ ディスプレイでは動作しないことがあります。"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"アプリはセカンダリ ディスプレイでの起動に対応していません。"</string> <string name="accessibility_divider" msgid="703810061635792791">"分割画面の分割線"</string> + <string name="divider_title" msgid="5482989479865361192">"分割画面の分割線"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"左全画面"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"左 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"バブル"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ふきだしが非表示になっています。"</string> - <string name="restart_button_description" msgid="5887656107651190519">"タップしてこのアプリを再起動すると、全画面表示になります。"</string> + <string name="restart_button_description" msgid="6712141648865547958">"タップしてこのアプリを再起動すると、表示が適切になります。"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"アプリによっては縦向きにすると正常に動作します"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"スペースを最大限に活用するには、以下の方法のいずれかをお試しください"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"全画面表示にするにはデバイスを回転させてください"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"位置を変えるにはアプリの横をダブルタップしてください"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"表示を拡大して機能を強化"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"位置を変えるにはアプリの外側をダブルタップしてください"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"開くと詳細が表示されます。"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> + <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> + <string name="close_button_text" msgid="2913281996024033299">"閉じる"</string> + <string name="back_button_text" msgid="1469718707134137085">"戻る"</string> + <string name="handle_text" msgid="1766582106752184456">"ハンドル"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"全画面表示"</string> + <string name="desktop_text" msgid="1077633567027630454">"デスクトップ モード"</string> + <string name="split_screen_text" msgid="1396336058129570886">"分割画面"</string> + <string name="more_button_text" msgid="3655388105592893530">"その他"</string> + <string name="float_button_text" msgid="9221657008391364581">"フローティング"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml index e58e7bf6fabc..2a79e3c651a1 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ピクチャー イン ピクチャー"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無題の番組)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP を閉じる"</string> + <string name="pip_close" msgid="2955969519031223530">"閉じる"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全画面表示"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP を移動"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP を開く"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP を閉じる"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" コントロールにアクセス: "<annotation icon="home_icon">" ホーム "</annotation>" を 2 回押します"</string> + <string name="pip_move" msgid="158770205886688553">"移動"</string> + <string name="pip_expand" msgid="1051966011679297308">"開く"</string> + <string name="pip_collapse" msgid="3903295106641385962">"閉じる"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"コントロールにアクセス: "<annotation icon="home_icon">" ホーム "</annotation>" を 2 回押します"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ピクチャー イン ピクチャーのメニューです。"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"左に移動"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"右に移動"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"上に移動"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"下に移動"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"完了"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index bff86fa6ffe2..848be3f86392 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"პარამეტრები"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"გაყოფილ ეკრანში შესვლა"</string> <string name="pip_menu_title" msgid="5393619322111827096">"მენიუ"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"„ეკრანი ეკრანში“ რეჟიმის მენიუ"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> იყენებს რეჟიმს „ეკრანი ეკრანში“"</string> <string name="pip_notification_message" msgid="8854051911700302620">"თუ არ გსურთ, რომ <xliff:g id="NAME">%s</xliff:g> ამ ფუნქციას იყენებდეს, აქ შეხებით შეგიძლიათ გახსნათ პარამეტრები და გამორთოთ ის."</string> <string name="pip_play" msgid="3496151081459417097">"დაკვრა"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"გადანახვის გაუქმება"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"აპს არ გააჩნია მეორეული ეკრანის მხარდაჭერა."</string> <string name="accessibility_divider" msgid="703810061635792791">"გაყოფილი ეკრანის რეჟიმის გამყოფი"</string> + <string name="divider_title" msgid="5482989479865361192">"ეკრანის გაყოფის გამყოფი"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"მარცხენა ნაწილის სრულ ეკრანზე გაშლა"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"მარცხენა ეკრანი — 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"მარცხენა ეკრანი — 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ბუშტი"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"მართვა"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ბუშტი დაიხურა."</string> - <string name="restart_button_description" msgid="5887656107651190519">"შეეხეთ ამ აპის გადასატვირთად და გადადით სრულ ეკრანზე."</string> + <string name="restart_button_description" msgid="6712141648865547958">"შეეხეთ, რომ გადატვირთოთ ეს აპი უკეთესი ხედისთვის."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"ზოგიერთი აპი უკეთ მუშაობს პორტრეტის რეჟიმში"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"გამოცადეთ ამ ვარიანტებიდან ერთ-ერთი, რათა მაქსიმალურად ისარგებლოთ თქვენი მეხსიერებით"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"მოატრიალეთ თქვენი მოწყობილობა სრული ეკრანის გასაშლელად"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ორმაგად შეეხეთ აპის გვერდითა სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"მეტის ნახვა და გაკეთება"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ორმაგად შეეხეთ აპის გარშემო სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"გასაგებია"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"დამატებითი ინფორმაციისთვის გააფართოეთ."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string> + <string name="minimize_button_text" msgid="271592547935841753">"ჩაკეცვა"</string> + <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string> + <string name="back_button_text" msgid="1469718707134137085">"უკან"</string> + <string name="handle_text" msgid="1766582106752184456">"იდენტიფიკატორი"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"სრულ ეკრანზე"</string> + <string name="desktop_text" msgid="1077633567027630454">"დესკტოპის რეჟიმი"</string> + <string name="split_screen_text" msgid="1396336058129570886">"ეკრანის გაყოფა"</string> + <string name="more_button_text" msgid="3655388105592893530">"სხვა"</string> + <string name="float_button_text" msgid="9221657008391364581">"ფარფატი"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml index b09686646c8b..58bae02120ff 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ეკრანი ეკრანში"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(პროგრამის სათაურის გარეშე)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP-ის დახურვა"</string> + <string name="pip_close" msgid="2955969519031223530">"დახურვა"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"სრულ ეკრანზე"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP გადატანა"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP-ის გაშლა"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP-ის ჩაკეცვა"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" მართვის საშუალებებზე წვდომისთვის ორმაგად დააჭირეთ "<annotation icon="home_icon">" მთავარ ღილაკს "</annotation></string> + <string name="pip_move" msgid="158770205886688553">"გადაადგილება"</string> + <string name="pip_expand" msgid="1051966011679297308">"გაშლა"</string> + <string name="pip_collapse" msgid="3903295106641385962">"ჩაკეცვა"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"სახლის მართვის საშუალებებზე წვდომისთვის ორმაგად დააჭირეთ "<annotation icon="home_icon">" მთავარ ღილაკს "</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"მენიუ „ეკრანი ეკრანში“."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"მარცხნივ გადატანა"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"მარჯვნივ გადატანა"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ზემოთ გადატანა"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ქვემოთ გადატანა"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"მზადაა"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index f57f3f581c85..8d08ccabb623 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Параметрлер"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Бөлінген экранға кіру"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Mәзір"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"\"Сурет ішіндегі сурет\" мәзірі"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"суреттегі сурет\" режимінде"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> деген пайдаланушының бұл мүмкіндікті пайдалануын қаламасаңыз, параметрлерді түртіп ашыңыз да, оларды өшіріңіз."</string> <string name="pip_play" msgid="3496151081459417097">"Ойнату"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Көрсету"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Қодланба бөлінген экранды қолдамайды."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бұл қолданбаны тек 1 терезеден ашуға болады."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Қолданба қосымша дисплейлерде іске қосуды қолдамайды."</string> <string name="accessibility_divider" msgid="703810061635792791">"Бөлінген экран бөлгіші"</string> + <string name="divider_title" msgid="5482989479865361192">"Бөлінген экран бөлгіші"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Сол жағын толық экранға шығару"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% сол жақта"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% сол жақта"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Көпіршік"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Басқару"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Қалқыма хабар жабылды."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Бұл қолданбаны қайта қосып, толық экранға өту үшін түртіңіз."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Ыңғайлы көріністі реттеу үшін қолданбаны түртіп, өшіріп қосыңыз."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Кейбір қолданба портреттік режимде жақсы жұмыс істейді"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Экранды тиімді пайдалану үшін мына опциялардың бірін байқап көріңіз."</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Толық экранға ауысу үшін құрылғыңызды бұрыңыз."</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Қолданбаның орнын ауыстыру үшін жанынан екі рет түртіңіз."</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Қосымша ақпаратты қарап, әрекеттер жасау"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлу үшін басқа қолданбаға сүйреңіз."</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Қолданбаның орнын өзгерту үшін одан тыс жерді екі рет түртіңіз."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түсінікті"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толығырақ ақпарат алу үшін терезені жайыңыз."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string> + <string name="close_button_text" msgid="2913281996024033299">"Жабу"</string> + <string name="back_button_text" msgid="1469718707134137085">"Артқа"</string> + <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Толық экран"</string> + <string name="desktop_text" msgid="1077633567027630454">"Компьютер режимі"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлу"</string> + <string name="more_button_text" msgid="3655388105592893530">"Қосымша"</string> + <string name="float_button_text" msgid="9221657008391364581">"Қалқыма"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml index 7bade0dff0d9..df5f6171b11b 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Суреттегі сурет"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Атаусыз бағдарлама)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP жабу"</string> + <string name="pip_close" msgid="2955969519031223530">"Жабу"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Толық экран"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP клипін жылжыту"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP терезесін жаю"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP терезесін жию"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Басқару элементтері: "<annotation icon="home_icon">" НЕГІЗГІ ЭКРАН "</annotation>" түймесін екі рет басыңыз."</string> + <string name="pip_move" msgid="158770205886688553">"Жылжыту"</string> + <string name="pip_expand" msgid="1051966011679297308">"Жаю"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Жию"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Басқару: "<annotation icon="home_icon">" НЕГІЗГІ ЭКРАН "</annotation>" түймесін екі рет басыңыз."</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"\"Сурет ішіндегі сурет\" мәзірі."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Солға жылжыту"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Оңға жылжыту"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Жоғары жылжыту"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Төмен жылжыту"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Дайын"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 5c04f881fe0e..7c4ea57e81d8 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"ការកំណត់"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"ចូលមុខងារបំបែកអេក្រង់"</string> <string name="pip_menu_title" msgid="5393619322111827096">"ម៉ឺនុយ"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ម៉ឺនុយរូបក្នុងរូប"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ស្ថិតក្នុងមុខងាររូបក្នុងរូប"</string> <string name="pip_notification_message" msgid="8854051911700302620">"ប្រសិនបើអ្នកមិនចង់ឲ្យ <xliff:g id="NAME">%s</xliff:g> ប្រើមុខងារនេះ សូមចុចបើកការកំណត់ រួចបិទវា។"</string> <string name="pip_play" msgid="3496151081459417097">"លេង"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ឈប់លាក់ជាបណ្ដោះអាសន្ន"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"កម្មវិធីអាចនឹងមិនដំណើរការជាមួយមុខងារបំបែកអេក្រង់ទេ។"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"កម្មវិធីមិនគាំទ្រអេក្រង់បំបែកជាពីរទេ"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"កម្មវិធីនេះអាចបើកនៅក្នុងវិនដូតែ 1 ប៉ុណ្ណោះ។"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"កម្មវិធីនេះប្រហែលជាមិនដំណើរការនៅលើអេក្រង់បន្ទាប់បន្សំទេ។"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"កម្មវិធីនេះមិនអាចចាប់ផ្តើមនៅលើអេក្រង់បន្ទាប់បន្សំបានទេ។"</string> <string name="accessibility_divider" msgid="703810061635792791">"កម្មវិធីចែកអេក្រង់បំបែក"</string> + <string name="divider_title" msgid="5482989479865361192">"បន្ទាត់ខណ្ឌចែកអេក្រង់បំបែក"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"អេក្រង់ពេញខាងឆ្វេង"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ឆ្វេង 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ឆ្វេង 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ពពុះ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"គ្រប់គ្រង"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"បានច្រានចោលសារលេចឡើង។"</string> - <string name="restart_button_description" msgid="5887656107651190519">"ចុចដើម្បីចាប់ផ្ដើមកម្មវិធីនេះឡើងវិញ រួចចូលប្រើពេញអេក្រង់។"</string> + <string name="restart_button_description" msgid="6712141648865547958">"ចុចដើម្បីចាប់ផ្ដើមកម្មវិធីនេះឡើងវិញសម្រាប់ទិដ្ឋភាពកាន់តែប្រសើរ។"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"កម្មវិធីមួយចំនួនដំណើរការបានប្រសើរបំផុតក្នុងទិសដៅបញ្ឈរ"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"សាកល្បងជម្រើសមួយក្នុងចំណោមទាំងនេះ ដើម្បីទទួលបានអត្ថប្រយោជន៍ច្រើនបំផុតពីកន្លែងទំនេររបស់អ្នក"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"បង្វិលឧបករណ៍របស់អ្នក ដើម្បីចូលប្រើអេក្រង់ពេញ"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ចុចពីរដងនៅជាប់កម្មវិធីណាមួយ ដើម្បីប្ដូរទីតាំងកម្មវិធីនោះ"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"មើលឃើញ និងធ្វើបានកាន់តែច្រើន"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"អូសកម្មវិធីមួយទៀតចូល ដើម្បីប្រើមុខងារបំបែកអេក្រង់"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ចុចពីរដងនៅក្រៅកម្មវិធី ដើម្បីប្ដូរទីតាំងកម្មវិធីនោះ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"យល់ហើយ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string> + <string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string> + <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string> + <string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string> + <string name="handle_text" msgid="1766582106752184456">"ឈ្មោះអ្នកប្រើប្រាស់"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"អេក្រង់ពេញ"</string> + <string name="desktop_text" msgid="1077633567027630454">"មុខងារកុំព្យូទ័រ"</string> + <string name="split_screen_text" msgid="1396336058129570886">"មុខងារបំបែកអេក្រង់"</string> + <string name="more_button_text" msgid="3655388105592893530">"ច្រើនទៀត"</string> + <string name="float_button_text" msgid="9221657008391364581">"អណ្ដែត"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-km/strings_tv.xml b/libs/WindowManager/Shell/res/values-km/strings_tv.xml index 721be1fc1650..a3c7e22f268a 100644 --- a/libs/WindowManager/Shell/res/values-km/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-km/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"រូបក្នុងរូប"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(កម្មវិធីគ្មានចំណងជើង)"</string> - <string name="pip_close" msgid="9135220303720555525">"បិទ PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"បិទ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ពេញអេក្រង់"</string> - <string name="pip_move" msgid="1544227837964635439">"ផ្លាស់ទី PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"ពង្រីក PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"បង្រួម PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" ចុចពីរដងលើ"<annotation icon="home_icon">"ប៊ូតុងដើម"</annotation>" ដើម្បីបើកផ្ទាំងគ្រប់គ្រង"</string> + <string name="pip_move" msgid="158770205886688553">"ផ្លាស់ទី"</string> + <string name="pip_expand" msgid="1051966011679297308">"ពង្រីក"</string> + <string name="pip_collapse" msgid="3903295106641385962">"បង្រួម"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"ចុចពីរដងលើ"<annotation icon="home_icon">"ប៊ូតុងដើម"</annotation>" ដើម្បីបើកផ្ទាំងគ្រប់គ្រង"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ម៉ឺនុយរូបក្នុងរូប"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ផ្លាស់ទីទៅឆ្វេង"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ផ្លាស់ទីទៅស្តាំ"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ផ្លាស់ទីឡើងលើ"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ផ្លាស់ទីចុះក្រោម"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"រួចរាល់"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index e91383caa009..72906174a65c 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"ಸ್ಪ್ಲಿಟ್-ಸ್ಕ್ರೀನ್ಗೆ ಪ್ರವೇಶಿಸಿ"</string> <string name="pip_menu_title" msgid="5393619322111827096">"ಮೆನು"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರ ಮೆನು"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವಾಗಿದೆ"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ಈ ವೈಶಿಷ್ಟ್ಯ ಬಳಸುವುದನ್ನು ನೀವು ಬಯಸದಿದ್ದರೆ, ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ತೆರೆಯಲು ಮತ್ತು ಅದನ್ನು ಆಫ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string> <string name="pip_play" msgid="3496151081459417097">"ಪ್ಲೇ"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ಅನ್ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"ವಿಭಜಿಸಿದ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ಅಪ್ಲಿಕೇಶನ್ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ಸೆಕೆಂಡರಿ ಡಿಸ್ಪ್ಲೇಗಳಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ಸೆಕೆಂಡರಿ ಡಿಸ್ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string> <string name="accessibility_divider" msgid="703810061635792791">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string> + <string name="divider_title" msgid="5482989479865361192">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ಎಡ ಪೂರ್ಣ ಪರದೆ"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% ಎಡಕ್ಕೆ"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% ಎಡಕ್ಕೆ"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ಬಬಲ್"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ನಿರ್ವಹಿಸಿ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ಬಬಲ್ ವಜಾಗೊಳಿಸಲಾಗಿದೆ."</string> - <string name="restart_button_description" msgid="5887656107651190519">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಮತ್ತು ಪೂರ್ಣ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ನೋಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string> + <string name="restart_button_description" msgid="6712141648865547958">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"ಕೆಲವು ಆ್ಯಪ್ಗಳು ಪೋರ್ಟ್ರೇಟ್ ಮೋಡ್ನಲ್ಲಿ ಅತ್ಯುತ್ತಮವಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತವೆ"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ನಿಮ್ಮ ಸ್ಥಳಾವಕಾಶದ ಅತಿಹೆಚ್ಚು ಪ್ರಯೋಜನ ಪಡೆಯಲು ಈ ಆಯ್ಕೆಗಳಲ್ಲಿ ಒಂದನ್ನು ಬಳಸಿ ನೋಡಿ"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್ಗೆ ಹೋಗಲು ನಿಮ್ಮ ಸಾಧನವನ್ನು ತಿರುಗಿಸಿ"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಪಕ್ಕದಲ್ಲಿ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ನೋಡಿ ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಿ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್ನಲ್ಲಿ ಎಳೆಯಿರಿ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಹೊರಗೆ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ಸರಿ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ವಿಸ್ತೃತಗೊಳಿಸಿ."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string> + <string name="minimize_button_text" msgid="271592547935841753">"ಕುಗ್ಗಿಸಿ"</string> + <string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string> + <string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string> + <string name="handle_text" msgid="1766582106752184456">"ಹ್ಯಾಂಡಲ್"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"ಫುಲ್ಸ್ಕ್ರೀನ್"</string> + <string name="desktop_text" msgid="1077633567027630454">"ಡೆಸ್ಕ್ಟಾಪ್ ಮೋಡ್"</string> + <string name="split_screen_text" msgid="1396336058129570886">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string> + <string name="more_button_text" msgid="3655388105592893530">"ಇನ್ನಷ್ಟು"</string> + <string name="float_button_text" msgid="9221657008391364581">"ಫ್ಲೋಟ್"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml index 8310c8a1169c..3dfe573a6506 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರ"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ಶೀರ್ಷಿಕೆ ರಹಿತ ಕಾರ್ಯಕ್ರಮ)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP ಮುಚ್ಚಿ"</string> + <string name="pip_close" msgid="2955969519031223530">"ಮುಚ್ಚಿರಿ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ಪೂರ್ಣ ಪರದೆ"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP ಅನ್ನು ಸರಿಸಿ"</string> - <string name="pip_expand" msgid="7605396312689038178">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವನ್ನು ವಿಸ್ತರಿಸಿ"</string> - <string name="pip_collapse" msgid="5732233773786896094">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವನ್ನು ಕುಗ್ಗಿಸಿ"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" ಕಂಟ್ರೋಲ್ಗಳಿಗಾಗಿ "<annotation icon="home_icon">" ಹೋಮ್ "</annotation>" ಅನ್ನು ಎರಡು ಬಾರಿ ಒತ್ತಿ"</string> + <string name="pip_move" msgid="158770205886688553">"ಸರಿಸಿ"</string> + <string name="pip_expand" msgid="1051966011679297308">"ವಿಸ್ತೃತಗೊಳಿಸಿ"</string> + <string name="pip_collapse" msgid="3903295106641385962">"ಕುಗ್ಗಿಸಿ"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"ಕಂಟ್ರೋಲ್ಗಳಿಗಾಗಿ "<annotation icon="home_icon">"ಹೋಮ್"</annotation>" ಅನ್ನು ಎರಡು ಬಾರಿ ಒತ್ತಿರಿ"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರ ಮೆನು."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ಎಡಕ್ಕೆ ಸರಿಸಿ"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ಬಲಕ್ಕೆ ಸರಿಸಿ"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ಮೇಲಕ್ಕೆ ಸರಿಸಿ"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ಕೆಳಗೆ ಸರಿಸಿ"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ಮುಗಿದಿದೆ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index 104ba3f22c96..59b405ff1e72 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"설정"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"화면 분할 모드로 전환"</string> <string name="pip_menu_title" msgid="5393619322111827096">"메뉴"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"PIP 모드 메뉴"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>에서 PIP 사용 중"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g>에서 이 기능이 사용되는 것을 원하지 않는 경우 탭하여 설정을 열고 기능을 사용 중지하세요."</string> <string name="pip_play" msgid="3496151081459417097">"재생"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"숨기기 취소"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"앱이 분할 화면에서 작동하지 않을 수 있습니다."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"앱이 화면 분할을 지원하지 않습니다."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"이 앱은 창 1개에서만 열 수 있습니다."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"앱이 보조 디스플레이에서의 실행을 지원하지 않습니다."</string> <string name="accessibility_divider" msgid="703810061635792791">"화면 분할기"</string> + <string name="divider_title" msgid="5482989479865361192">"화면 분할기"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"왼쪽 화면 전체화면"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"왼쪽 화면 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"왼쪽 화면 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"버블"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"관리"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"대화창을 닫았습니다."</string> - <string name="restart_button_description" msgid="5887656107651190519">"탭하여 이 앱을 다시 시작하고 전체 화면으로 이동합니다."</string> + <string name="restart_button_description" msgid="6712141648865547958">"보기를 개선하려면 탭하여 앱을 다시 시작합니다."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"일부 앱은 세로 모드에서 가장 잘 작동함"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"공간을 최대한 이용할 수 있도록 이 옵션 중 하나를 시도해 보세요."</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"전체 화면 모드로 전환하려면 기기를 회전하세요."</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"앱 위치를 조정하려면 앱 옆을 두 번 탭하세요."</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"더 많은 정보를 보고 더 많은 작업을 처리하세요"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"앱 위치를 조정하려면 앱 외부를 두 번 탭합니다."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"확인"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"추가 정보는 펼쳐서 확인하세요."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string> + <string name="minimize_button_text" msgid="271592547935841753">"최소화"</string> + <string name="close_button_text" msgid="2913281996024033299">"닫기"</string> + <string name="back_button_text" msgid="1469718707134137085">"뒤로"</string> + <string name="handle_text" msgid="1766582106752184456">"핸들"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"전체 화면"</string> + <string name="desktop_text" msgid="1077633567027630454">"데스크톱 모드"</string> + <string name="split_screen_text" msgid="1396336058129570886">"화면 분할"</string> + <string name="more_button_text" msgid="3655388105592893530">"더보기"</string> + <string name="float_button_text" msgid="9221657008391364581">"플로팅"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml index a3e055a515a1..969a68d0346e 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"PIP 모드"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(제목 없는 프로그램)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP 닫기"</string> + <string name="pip_close" msgid="2955969519031223530">"닫기"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"전체화면"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP 이동"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP 펼치기"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP 접기"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" 제어 메뉴에 액세스하려면 "<annotation icon="home_icon">" 홈 "</annotation>"을 두 번 누르세요."</string> + <string name="pip_move" msgid="158770205886688553">"이동"</string> + <string name="pip_expand" msgid="1051966011679297308">"펼치기"</string> + <string name="pip_collapse" msgid="3903295106641385962">"접기"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"제어 메뉴에 액세스하려면 "<annotation icon="home_icon">"홈"</annotation>"을 두 번 누르세요."</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"PIP 모드 메뉴입니다."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"왼쪽으로 이동"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"오른쪽으로 이동"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"위로 이동"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"아래로 이동"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"완료"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 8203622a33fc..528c51d2966e 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Жөндөөлөр"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Экранды бөлүү режимине өтүү"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Сүрөт ичиндеги сүрөт менюсу"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> – сүрөт ичиндеги сүрөт"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Эгер <xliff:g id="NAME">%s</xliff:g> колдонмосу бул функцияны пайдаланбасын десеңиз, жөндөөлөрдү ачып туруп, аны өчүрүп коюңуз."</string> <string name="pip_play" msgid="3496151081459417097">"Ойнотуу"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Сейфтен чыгаруу"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Колдонмодо экран бөлүнбөшү мүмкүн."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Колдонмодо экран бөлүнбөйт."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бул колдонмону 1 терезеде гана ачууга болот."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Колдонмо кошумча экранда иштебей коюшу мүмкүн."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Колдонмону кошумча экрандарда иштетүүгө болбойт."</string> <string name="accessibility_divider" msgid="703810061635792791">"Экранды бөлгүч"</string> + <string name="divider_title" msgid="5482989479865361192">"Экранды бөлгүч"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Сол жактагы экранды толук экран режимине өткөрүү"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Сол жактагы экранды 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Сол жактагы экранды 50%"</string> @@ -56,9 +59,9 @@ <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="APP_NAME">%2$s</xliff:g> колдонмосунан <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="APP_NAME">%2$s</xliff:g> жана дагы <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> колдонмодон <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Жогорку сол жакка жылдыруу"</string> - <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Жогорку оң жакка жылдырыңыз"</string> + <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Жогорку оң жакка жылдыруу"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Төмөнкү сол жакка жылдыруу"</string> - <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төмөнкү оң жакка жылдырыңыз"</string> + <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төмөнкү оң жакка жылдыруу"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> жөндөөлөрү"</string> <string name="bubble_dismiss_text" msgid="8816558050659478158">"Калкып чыкма билдирмени жабуу"</string> <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Жазышууда калкып чыкма билдирмелер көрүнбөсүн"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Көбүк"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Башкаруу"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Калкып чыкма билдирме жабылды."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Бул колдонмону өчүрүп күйгүзүп, толук экранга өтүү үчүн таптап коюңуз."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Жакшыраак көрүү үчүн бул колдонмону өчүрүп күйгүзүңүз. Ал үчүн таптап коюңуз."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Айрым колдонмолорду тигинен иштетүү туура болот"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Иш чөйрөсүнүн бардык мүмкүнчүлүктөрүн пайдалануу үчүн бул параметрлердин бирин колдонуп көрүңүз"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Толук экран режимине өтүү үчүн түзмөктү буруңуз"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Колдонмонун ракурсун өзгөртүү үчүн анын тушуна эки жолу басыңыз"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Көрүп, көбүрөөк нерселерди жасаңыз"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Колдонмону жылдыруу үчүн сырт жагын эки жолу таптаңыз"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түшүндүм"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толук маалымат алуу үчүн жайып көрүңүз."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Кичирейтүү"</string> + <string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string> + <string name="back_button_text" msgid="1469718707134137085">"Артка"</string> + <string name="handle_text" msgid="1766582106752184456">"Маркер"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Толук экран"</string> + <string name="desktop_text" msgid="1077633567027630454">"Компьютер режими"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлүү"</string> + <string name="more_button_text" msgid="3655388105592893530">"Дагы"</string> + <string name="float_button_text" msgid="9221657008391364581">"Калкыма"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml index 887ac52c8e43..68262e521f4a 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Сүрөттөгү сүрөт"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Аталышы жок программа)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP\'ти жабуу"</string> + <string name="pip_close" msgid="2955969519031223530">"Жабуу"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Толук экран"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP\'ти жылдыруу"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP\'ти жайып көрсөтүү"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP\'ти жыйыштыруу"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Башкаруу элементтерин ачуу үчүн "<annotation icon="home_icon">" БАШКЫ БЕТ "</annotation>" баскычын эки жолу басыңыз"</string> + <string name="pip_move" msgid="158770205886688553">"Жылдыруу"</string> + <string name="pip_expand" msgid="1051966011679297308">"Жайып көрсөтүү"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Жыйыштыруу"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Башкаруу элементтерин ачуу үчүн "<annotation icon="home_icon">" БАШКЫ БЕТ "</annotation>" баскычын эки жолу басыңыз"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Сүрөт ичиндеги сүрөт менюсу."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Солго жылдыруу"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Оңго жылдыруу"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Жогору жылдыруу"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Төмөн жылдыруу"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Бүттү"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index 24396786f9d8..d5ea3cf37c8b 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"ການຕັ້ງຄ່າ"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"ເຂົ້າການແບ່ງໜ້າຈໍ"</string> <string name="pip_menu_title" msgid="5393619322111827096">"ເມນູ"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ເມນູການສະແດງຜົນຊ້ອນກັນ"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ແມ່ນເປັນການສະແດງຜົນຫຼາຍຢ່າງພ້ອມກັນ"</string> <string name="pip_notification_message" msgid="8854051911700302620">"ຫາກທ່ານບໍ່ຕ້ອງການ <xliff:g id="NAME">%s</xliff:g> ໃຫ້ໃຊ້ຄຸນສົມບັດນີ້, ໃຫ້ແຕະເພື່ອເປີດການຕັ້ງຄ່າ ແລ້ວປິດມັນໄວ້."</string> <string name="pip_play" msgid="3496151081459417097">"ຫຼິ້ນ"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ເອົາອອກຈາກບ່ອນເກັບສ່ວນຕົວ"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບການແບ່ງໜ້າຈໍ."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ແອັບບໍ່ຮອງຮັບໜ້າຈໍແບບແຍກກັນ."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ແອັບບໍ່ຮອງຮັບການເປີດໃນໜ້າຈໍທີສອງ."</string> <string name="accessibility_divider" msgid="703810061635792791">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string> + <string name="divider_title" msgid="5482989479865361192">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ເຕັມໜ້າຈໍຊ້າຍ"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ຊ້າຍ 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ຊ້າຍ 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ຟອງ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ຈັດການ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ປິດ Bubble ໄສ້ແລ້ວ."</string> - <string name="restart_button_description" msgid="5887656107651190519">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ ແລະ ໃຊ້ແບບເຕັມຈໍ."</string> + <string name="restart_button_description" msgid="6712141648865547958">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ເພື່ອມຸມມອງທີ່ດີຂຶ້ນ."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"ແອັບບາງຢ່າງເຮັດວຽກໄດ້ດີທີ່ສຸດໃນໂໝດລວງຕັ້ງ"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ໃຫ້ລອງຕົວເລືອກໃດໜຶ່ງເຫຼົ່ານີ້ເພື່ອໃຊ້ປະໂຫຍດຈາກພື້ນທີ່ຂອງທ່ານໃຫ້ໄດ້ສູງສຸດ"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ໝຸນອຸປະກອນຂອງທ່ານເພື່ອໃຊ້ແບບເຕັມຈໍ"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ແຕະສອງເທື່ອໃສ່ຖັດຈາກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ເບິ່ງ ແລະ ເຮັດຫຼາຍຂຶ້ນ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ລາກແອັບອື່ນເຂົ້າມາເພື່ອແບ່ງໜ້າຈໍ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ແຕະສອງເທື່ອໃສ່ນອກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ເຂົ້າໃຈແລ້ວ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ຂະຫຍາຍເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string> + <string name="minimize_button_text" msgid="271592547935841753">"ຫຍໍ້ລົງ"</string> + <string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string> + <string name="back_button_text" msgid="1469718707134137085">"ກັບຄືນ"</string> + <string name="handle_text" msgid="1766582106752184456">"ມືບັງຄັບ"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"ເຕັມຈໍ"</string> + <string name="desktop_text" msgid="1077633567027630454">"ໂໝດເດັສທັອບ"</string> + <string name="split_screen_text" msgid="1396336058129570886">"ແບ່ງໜ້າຈໍ"</string> + <string name="more_button_text" msgid="3655388105592893530">"ເພີ່ມເຕີມ"</string> + <string name="float_button_text" msgid="9221657008391364581">"ລອຍ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml index 91c4a033356d..b84c83555ea0 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ການສະແດງຜົນຊ້ອນກັນ"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ໂປຣແກຣມບໍ່ມີຊື່)"</string> - <string name="pip_close" msgid="9135220303720555525">"ປິດ PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"ປິດ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ເຕັມໜ້າຈໍ"</string> - <string name="pip_move" msgid="1544227837964635439">"ຍ້າຍ PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"ຂະຫຍາຍ PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"ຫຍໍ້ PIP ລົງ"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" ກົດ "<annotation icon="home_icon">" HOME "</annotation>" ສອງເທື່ອສຳລັບການຄວບຄຸມ"</string> + <string name="pip_move" msgid="158770205886688553">"ຍ້າຍ"</string> + <string name="pip_expand" msgid="1051966011679297308">"ຂະຫຍາຍ"</string> + <string name="pip_collapse" msgid="3903295106641385962">"ຫຍໍ້"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"ກົດ "<annotation icon="home_icon">"HOME"</annotation>" ສອງເທື່ອສຳລັບການຄວບຄຸມ"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ເມນູການສະແດງຜົນຊ້ອນກັນ."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ຍ້າຍໄປຊ້າຍ"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ຍ້າຍໄປຂວາ"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ຍ້າຍຂຶ້ນ"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ຍ້າຍລົງ"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ແລ້ວໆ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index e2ae643ad308..922f5b59a703 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Nustatymai"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Įjungti išskaidyto ekrano režimą"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Vaizdo vaizde meniu"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> rodom. vaizdo vaizde"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Jei nenorite, kad „<xliff:g id="NAME">%s</xliff:g>“ naudotų šią funkciją, palietę atidarykite nustatymus ir išjunkite ją."</string> <string name="pip_play" msgid="3496151081459417097">"Leisti"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Nebeslėpti"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Programa gali neveikti naudojant išskaidyto ekrano režimą."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programoje nepalaikomas skaidytas ekranas."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šią programą galima atidaryti tik viename lange."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Programa gali neveikti antriniame ekrane."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programa nepalaiko paleisties antriniuose ekranuose."</string> <string name="accessibility_divider" msgid="703810061635792791">"Skaidyto ekrano daliklis"</string> + <string name="divider_title" msgid="5482989479865361192">"Skaidyto ekrano daliklis"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Kairysis ekranas viso ekrano režimu"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kairysis ekranas 70 %"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kairysis ekranas 50 %"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Debesėlis"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Tvarkyti"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Debesėlio atsisakyta."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Palieskite, kad paleistumėte iš naujo šią programą ir įjungtumėte viso ekrano režimą."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Palieskite, kad iš naujo paleistumėte šią programą ir matytumėte aiškiau."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Iškilo problemų dėl kameros?\nPalieskite, kad pritaikytumėte iš naujo"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepavyko pataisyti?\nPalieskite, kad grąžintumėte"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nėra jokių problemų dėl kameros? Palieskite, kad atsisakytumėte."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Kai kurios programos geriausiai veikia stačiuoju režimu"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Pabandykite naudoti vieną iš šių parinkčių, kad išnaudotumėte visą vietą"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pasukite įrenginį, kad įjungtumėte viso ekrano režimą"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dukart palieskite šalia programos, kad pakeistumėte jos poziciją"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daugiau turinio ir funkcijų"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Vilkite kitoje programoje, kad galėtumėte naudoti išskaidyto ekrano režimą"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dukart palieskite už programos ribų, kad pakeistumėte jos poziciją"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Išskleiskite, jei reikia daugiau informacijos."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Sumažinti"</string> + <string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string> + <string name="back_button_text" msgid="1469718707134137085">"Atgal"</string> + <string name="handle_text" msgid="1766582106752184456">"Rankenėlė"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Visas ekranas"</string> + <string name="desktop_text" msgid="1077633567027630454">"Stalinio kompiuterio režimas"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Išskaidyto ekrano režimas"</string> + <string name="more_button_text" msgid="3655388105592893530">"Daugiau"</string> + <string name="float_button_text" msgid="9221657008391364581">"Slankusis langas"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml index 04265ca01b48..0537553cc36a 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Vaizdas vaizde"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa be pavadinimo)"</string> - <string name="pip_close" msgid="9135220303720555525">"Uždaryti PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Uždaryti"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Visas ekranas"</string> - <string name="pip_move" msgid="1544227837964635439">"Perkelti PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Iškleisti PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Sutraukti PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Jei reikia valdiklių, dukart paspauskite "<annotation icon="home_icon">"PAGRINDINIS"</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Perkelti"</string> + <string name="pip_expand" msgid="1051966011679297308">"Išskleisti"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Sutraukti"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Jei reikia valdiklių, dukart pasp. "<annotation icon="home_icon">"PAGRINDINIS"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Vaizdo vaizde meniu."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Perkelti kairėn"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Perkelti dešinėn"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Perkelti aukštyn"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Perkelti žemyn"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Atlikta"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index a77160bc262a..08ac9280bbab 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Iestatījumi"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Piekļūt ekrāna sadalīšanas režīmam"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Izvēlne"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Izvēlne attēlam attēlā"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ir attēlā attēlā"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ja nevēlaties lietotnē <xliff:g id="NAME">%s</xliff:g> izmantot šo funkciju, pieskarieties, lai atvērtu iestatījumus un izslēgtu funkciju."</string> <string name="pip_play" msgid="3496151081459417097">"Atskaņot"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Rādīt"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Lietotnē netiek atbalstīta ekrāna sadalīšana."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šo lietotni var atvērt tikai vienā logā."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Lietotne, iespējams, nedarbosies sekundārajā displejā."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Lietotnē netiek atbalstīta palaišana sekundārajos displejos."</string> <string name="accessibility_divider" msgid="703810061635792791">"Ekrāna sadalītājs"</string> + <string name="divider_title" msgid="5482989479865361192">"Ekrāna sadalītājs"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Kreisā daļa pa visu ekrānu"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Pa kreisi 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pa kreisi 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Burbulis"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Pārvaldīt"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbulis ir noraidīts."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Pieskarieties, lai restartētu šo lietotni un pārietu pilnekrāna režīmā."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Pieskarieties, lai restartētu šo lietotni un uzlabotu attēlojumu."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Vai ir problēmas ar kameru?\nPieskarieties, lai tās novērstu."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Vai problēma netika novērsta?\nPieskarieties, lai atjaunotu."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Vai nav problēmu ar kameru? Pieskarieties, lai nerādītu."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Dažas lietotnes vislabāk darbojas portreta režīmā"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Izmēģiniet vienu no šīm iespējām, lai efektīvi izmantotu pieejamo vietu"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pagrieziet ierīci, lai aktivizētu pilnekrāna režīmu"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Veiciet dubultskārienu blakus lietotnei, lai manītu tās pozīciju"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Uzziniet un paveiciet vairāk"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Lai izmantotu sadalītu ekrānu, ievelciet vēl vienu lietotni"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Lai pārvietotu lietotni, veiciet dubultskārienu ārpus lietotnes"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Izvērsiet, lai iegūtu plašāku informāciju."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimizēt"</string> + <string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string> + <string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string> + <string name="handle_text" msgid="1766582106752184456">"Turis"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Pilnekrāna režīms"</string> + <string name="desktop_text" msgid="1077633567027630454">"Darbvirsmas režīms"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Sadalīt ekrānu"</string> + <string name="more_button_text" msgid="3655388105592893530">"Vairāk"</string> + <string name="float_button_text" msgid="9221657008391364581">"Peldošs"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml index 8c6191e00833..13baa9bc46eb 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Attēls attēlā"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma bez nosaukuma)"</string> - <string name="pip_close" msgid="9135220303720555525">"Aizvērt PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Aizvērt"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pilnekrāna režīms"</string> - <string name="pip_move" msgid="1544227837964635439">"Pārvietot attēlu attēlā"</string> - <string name="pip_expand" msgid="7605396312689038178">"Izvērst “Attēls attēlā” logu"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Sakļaut “Attēls attēlā” logu"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Atvērt vadīklas: divreiz nospiediet pogu "<annotation icon="home_icon">"SĀKUMS"</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Pārvietot"</string> + <string name="pip_expand" msgid="1051966011679297308">"Izvērst"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Sakļaut"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Atvērt vadīklas: divreiz nospiediet pogu "<annotation icon="home_icon">"SĀKUMS"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Izvēlne attēlam attēlā."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Pārvietot pa kreisi"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Pārvietot pa labi"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Pārvietot augšup"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pārvietot lejup"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gatavs"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index bac0c9eee4c2..ae71ae90043f 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Поставки"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Влези во поделен екран"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Мени"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Мени за „Слика во слика“"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> е во слика во слика"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ако не сакате <xliff:g id="NAME">%s</xliff:g> да ја користи функцијава, допрете за да ги отворите поставките и да ја исклучите."</string> <string name="pip_play" msgid="3496151081459417097">"Пушти"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Прикажете"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Апликацијата може да не работи со поделен екран."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликацијата не поддржува поделен екран."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Апликацијава може да се отвори само во еден прозорец."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликацијата може да не функционира на друг екран."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликацијата не поддржува стартување на други екрани."</string> <string name="accessibility_divider" msgid="703810061635792791">"Разделник на поделен екран"</string> + <string name="divider_title" msgid="5482989479865361192">"Разделник на поделен екран"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Левиот на цел екран"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Левиот 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левиот 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Управувајте"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отфрлено."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Допрете за да ја рестартирате апликацијава и да ја отворите на цел екран."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Допрете за да ја рестартирате апликацијава за подобар приказ."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Некои апликации најдобро работат во режим на портрет"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Испробајте една од опцииве за да го извлечете максимумот од вашиот простор"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ротирајте го уредот за да отворите на цел екран"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Допрете двапати до некоја апликација за да ја преместите"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Погледнете и направете повеќе"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Повлечете во друга апликација за поделен екран"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Допрете двапати надвор од некоја апликација за да ја преместите"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Сфатив"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширете за повеќе информации."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Минимизирај"</string> + <string name="close_button_text" msgid="2913281996024033299">"Затвори"</string> + <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> + <string name="handle_text" msgid="1766582106752184456">"Прекар"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Цел екран"</string> + <string name="desktop_text" msgid="1077633567027630454">"Режим за компјутер"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Поделен екран"</string> + <string name="more_button_text" msgid="3655388105592893530">"Повеќе"</string> + <string name="float_button_text" msgid="9221657008391364581">"Лебдечко"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml index beef1fef862b..d7a9516bea7f 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Слика во слика"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без наслов)"</string> - <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Затвори"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Цел екран"</string> - <string name="pip_move" msgid="1544227837964635439">"Премести PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Прошири ја сликата во слика"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Собери ја сликата во слика"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Притиснете двапати на "<annotation icon="home_icon">" HOME "</annotation>" за контроли"</string> + <string name="pip_move" msgid="158770205886688553">"Премести"</string> + <string name="pip_expand" msgid="1051966011679297308">"Прошири"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Собери"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Притиснете двапати на "<annotation icon="home_icon">"HOME"</annotation>" за контроли"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Мени за „Слика во слика“."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Премести налево"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Премести надесно"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Премести нагоре"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Премести надолу"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Готово"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index de0f837fcd3f..092331284f61 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"ക്രമീകരണം"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"സ്ക്രീൻ വിഭജന മോഡിൽ പ്രവേശിക്കുക"</string> <string name="pip_menu_title" msgid="5393619322111827096">"മെനു"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ചിത്രത്തിനുള്ളിൽ ചിത്രം മെനു"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ചിത്രത്തിനുള്ളിൽ ചിത്രം രീതിയിലാണ്"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ഈ ഫീച്ചർ ഉപയോഗിക്കേണ്ടെങ്കിൽ, ടാപ്പ് ചെയ്ത് ക്രമീകരണം തുറന്ന് അത് ഓഫാക്കുക."</string> <string name="pip_play" msgid="3496151081459417097">"പ്ലേ ചെയ്യുക"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"അൺസ്റ്റാഷ് ചെയ്യൽ"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"സ്ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"സ്പ്ലിറ്റ്-സ്ക്രീനിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ഈ ആപ്പ് ഒരു വിൻഡോയിൽ മാത്രമേ തുറക്കാനാകൂ."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"രണ്ടാം ഡിസ്പ്ലേയിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"രണ്ടാം ഡിസ്പ്ലേകളിൽ സമാരംഭിക്കുന്നതിനെ ആപ്പ് അനുവദിക്കുന്നില്ല."</string> <string name="accessibility_divider" msgid="703810061635792791">"സ്പ്ലിറ്റ്-സ്ക്രീൻ ഡിവൈഡർ"</string> + <string name="divider_title" msgid="5482989479865361192">"സ്ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ഇടത് പൂർണ്ണ സ്ക്രീൻ"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ഇടത് 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ഇടത് 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ബബിൾ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"മാനേജ് ചെയ്യുക"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ബബിൾ ഡിസ്മിസ് ചെയ്തു."</string> - <string name="restart_button_description" msgid="5887656107651190519">"ഈ ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്ത് പൂർണ്ണ സ്ക്രീനിലേക്ക് മാറാൻ ടാപ്പ് ചെയ്യുക."</string> + <string name="restart_button_description" msgid="6712141648865547958">"മികച്ച കാഴ്ചയ്ക്കായി ഈ ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"ചില ആപ്പുകൾ പോർട്രെയ്റ്റിൽ മികച്ച രീതിയിൽ പ്രവർത്തിക്കുന്നു"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"നിങ്ങളുടെ ഇടം പരമാവധി പ്രയോജനപ്പെടുത്താൻ ഈ ഓപ്ഷനുകളിലൊന്ന് പരീക്ഷിക്കുക"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"പൂർണ്ണ സ്ക്രീനിലേക്ക് മാറാൻ ഈ ഉപകരണം റൊട്ടേറ്റ് ചെയ്യുക"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ഒരു ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ, അതിന് തൊട്ടടുത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"കൂടുതൽ കാണുക, ചെയ്യുക"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"സ്ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ അതിന് പുറത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"മനസ്സിലായി"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"കൂടുതൽ വിവരങ്ങൾക്ക് വികസിപ്പിക്കുക."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string> + <string name="minimize_button_text" msgid="271592547935841753">"ചെറുതാക്കുക"</string> + <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string> + <string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string> + <string name="handle_text" msgid="1766582106752184456">"ഹാൻഡിൽ"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"പൂർണ്ണസ്ക്രീൻ"</string> + <string name="desktop_text" msgid="1077633567027630454">"ഡെസ്ക്ടോപ്പ് മോഡ്"</string> + <string name="split_screen_text" msgid="1396336058129570886">"സ്ക്രീൻ വിഭജനം"</string> + <string name="more_button_text" msgid="3655388105592893530">"കൂടുതൽ"</string> + <string name="float_button_text" msgid="9221657008391364581">"ഫ്ലോട്ട്"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml index c2a532d09647..56f2b196421b 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ചിത്രത്തിനുള്ളിൽ ചിത്രം"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(പേരില്ലാത്ത പ്രോഗ്രാം)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP അടയ്ക്കുക"</string> + <string name="pip_close" msgid="2955969519031223530">"അടയ്ക്കുക"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"പൂര്ണ്ണ സ്ക്രീന്"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP നീക്കുക"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP വികസിപ്പിക്കുക"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP ചുരുക്കുക"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" നിയന്ത്രണങ്ങൾക്കായി "<annotation icon="home_icon">" ഹോം "</annotation>" രണ്ട് തവണ അമർത്തുക"</string> + <string name="pip_move" msgid="158770205886688553">"നീക്കുക"</string> + <string name="pip_expand" msgid="1051966011679297308">"വികസിപ്പിക്കുക"</string> + <string name="pip_collapse" msgid="3903295106641385962">"ചുരുക്കുക"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"നിയന്ത്രണങ്ങൾക്കായി "<annotation icon="home_icon">"ഹോം "</annotation>" രണ്ട് തവണ അമർത്തുക"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ചിത്രത്തിനുള്ളിൽ ചിത്രം മെനു."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ഇടത്തേക്ക് നീക്കുക"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"വലത്തേക്ക് നീക്കുക"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"മുകളിലേക്ക് നീക്കുക"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"താഴേക്ക് നീക്കുക"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"പൂർത്തിയായി"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index 1205306e0833..c1950a1d3241 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Тохиргоо"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Хуваасан дэлгэцийг оруулна уу"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Цэс"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Дэлгэц доторх дэлгэцийн цэс"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> дэлгэцэн доторх дэлгэцэд байна"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Та <xliff:g id="NAME">%s</xliff:g>-д энэ онцлогийг ашиглуулахыг хүсэхгүй байвал тохиргоог нээгээд, үүнийг унтраана уу."</string> <string name="pip_play" msgid="3496151081459417097">"Тоглуулах"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ил гаргах"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Апп хуваагдсан дэлгэц дээр ажиллахгүй байж болзошгүй."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Энэ апп нь дэлгэц хуваах тохиргоог дэмждэггүй."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апп хоёрдогч дэлгэцэд ажиллахгүй."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Аппыг хоёрдогч дэлгэцэд эхлүүлэх боломжгүй."</string> <string name="accessibility_divider" msgid="703810061635792791">"\"Дэлгэц хуваах\" хуваагч"</string> + <string name="divider_title" msgid="5482989479865361192">"\"Дэлгэцийг хуваах\" хуваагч"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Зүүн талын бүтэн дэлгэц"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Зүүн 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Зүүн 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Бөмбөлөг"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Удирдах"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Бөмбөлгийг үл хэрэгссэн."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Энэ аппыг дахин эхлүүлж, бүтэн дэлгэцэд орохын тулд товшино уу."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Харагдах байдлыг сайжруулахын тулд энэ аппыг товшиж, дахин эхлүүлнэ үү."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Зарим апп нь босоо чиглэлд хамгийн сайн ажилладаг"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Орон зайгаа сайтар ашиглахын тулд эдгээр сонголтуудын аль нэгийг туршиж үзээрэй"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Төхөөрөмжөө бүтэн дэлгэцээр үзэхийн тулд эргүүлнэ"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Аппыг дахин байрлуулахын тулд хажууд нь хоёр товшино"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Харж илүү ихийг хий"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Дэлгэцийг хуваахын тулд өөр апп руу чирнэ үү"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Аппыг дахин байрлуулахын тулд гадна талд нь хоёр товшино"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ойлголоо"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Нэмэлт мэдээлэл авах бол дэлгэнэ үү."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Багасгах"</string> + <string name="close_button_text" msgid="2913281996024033299">"Хаах"</string> + <string name="back_button_text" msgid="1469718707134137085">"Буцах"</string> + <string name="handle_text" msgid="1766582106752184456">"Бариул"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Бүтэн дэлгэц"</string> + <string name="desktop_text" msgid="1077633567027630454">"Дэлгэцийн горим"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Дэлгэцийг хуваах"</string> + <string name="more_button_text" msgid="3655388105592893530">"Бусад"</string> + <string name="float_button_text" msgid="9221657008391364581">"Хөвөгч"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml index bf8c59b57359..0e6dcca17e38 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Дэлгэц доторх дэлгэц"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Гарчиггүй хөтөлбөр)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP-г хаах"</string> + <string name="pip_close" msgid="2955969519031223530">"Хаах"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Бүтэн дэлгэц"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP-г зөөх"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP-г дэлгэх"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP-г хураах"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Хяналтад хандах бол "<annotation icon="home_icon">" HOME "</annotation>" дээр хоёр дарна уу"</string> + <string name="pip_move" msgid="158770205886688553">"Зөөх"</string> + <string name="pip_expand" msgid="1051966011679297308">"Дэлгэх"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Хураах"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Хяналтад хандах бол "<annotation icon="home_icon">"HOME"</annotation>" дээр хоёр дарна уу"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Дэлгэцэн доторх дэлгэцийн цэс."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Зүүн тийш зөөх"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Баруун тийш зөөх"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Дээш зөөх"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Доош зөөх"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Болсон"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index c91d06fdf3d5..29821f6a8bbc 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"सेटिंग्ज"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रीन एंटर करा"</string> <string name="pip_menu_title" msgid="5393619322111827096">"मेनू"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"चित्रात-चित्र मेनू"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> चित्रामध्ये चित्र मध्ये आहे"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g>ने हे वैशिष्ट्य वापरू नये असे तुम्हाला वाटत असल्यास, सेटिंग्ज उघडण्यासाठी टॅप करा आणि ते बंद करा."</string> <string name="pip_play" msgid="3496151081459417097">"प्ले करा"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्टॅश करा"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"अॅप कदाचित स्प्लिट स्क्रीनसह काम करू शकत नाही."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अॅप स्क्रीन-विभाजनास समर्थन देत नाही."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"हे अॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"दुसऱ्या डिस्प्लेवर अॅप कदाचित चालणार नाही."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"दुसऱ्या डिस्प्लेवर अॅप लाँच होणार नाही."</string> <string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रीन विभाजक"</string> + <string name="divider_title" msgid="5482989479865361192">"स्प्लिट-स्क्रीन विभाजक"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"डावी फुल स्क्रीन"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"डावी 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"डावी 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापित करा"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल डिसमिस केला."</string> - <string name="restart_button_description" msgid="5887656107651190519">"हे अॅप रीस्टार्ट करण्यासाठी आणि फुल स्क्रीन करण्यासाठी टॅप करा."</string> + <string name="restart_button_description" msgid="6712141648865547958">"अधिक चांगल्या व्ह्यूसाठी हे अॅप रीस्टार्ट करण्याकरिता टॅप करा."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"काही ॲप्स पोर्ट्रेटमध्ये सर्वोत्तम काम करतात"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"तुमच्या स्पेसचा पुरेपूर वापर करण्यासाठी, यांपैकी एक पर्याय वापरून पहा"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"फुल स्क्रीन करण्यासाठी, तुमचे डिव्हाइस फिरवा"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या शेजारी दोनदा टॅप करा"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पहा आणि आणखी बरेच काही करा"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट-स्क्रीन वापरण्यासाठी दुसऱ्या ॲपमध्ये ड्रॅग करा"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या बाहेर दोनदा टॅप करा"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"समजले"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"अधिक माहितीसाठी विस्तार करा."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string> + <string name="minimize_button_text" msgid="271592547935841753">"लहान करा"</string> + <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string> + <string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string> + <string name="handle_text" msgid="1766582106752184456">"हँडल"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"फुलस्क्रीन"</string> + <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string> + <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन"</string> + <string name="more_button_text" msgid="3655388105592893530">"आणखी"</string> + <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml index 5d519b7afe9a..8a8977979217 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"चित्रात-चित्र"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षक नसलेला कार्यक्रम)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP बंद करा"</string> + <string name="pip_close" msgid="2955969519031223530">"बंद करा"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रीन"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP हलवा"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP चा विस्तार करा"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP कोलॅप्स करा"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" नियंत्रणांसाठी "<annotation icon="home_icon">" होम "</annotation>" दोनदा दाबा"</string> + <string name="pip_move" msgid="158770205886688553">"हलवा"</string> + <string name="pip_expand" msgid="1051966011679297308">"विस्तार करा"</string> + <string name="pip_collapse" msgid="3903295106641385962">"कोलॅप्स करा"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"नियंत्रणांसाठी "<annotation icon="home_icon">"होम"</annotation>" दोनदा दाबा"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"चित्रात-चित्र मेनू."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"डावीकडे हलवा"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"उजवीकडे हलवा"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"वर हलवा"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"खाली हलवा"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"पूर्ण झाले"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index 652a9919d163..c3db19d46a55 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Tetapan"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Masuk skrin pisah"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu Gambar dalam Gambar"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> terdapat dalam gambar dalam gambar"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Jika anda tidak mahu <xliff:g id="NAME">%s</xliff:g> menggunakan ciri ini, ketik untuk membuka tetapan dan matikan ciri."</string> <string name="pip_play" msgid="3496151081459417097">"Main"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Tunjukkan"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Apl mungkin tidak berfungsi dengan skrin pisah."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Apl tidak menyokong skrin pisah."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Apl ini hanya boleh dibuka dalam 1 tetingkap."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Apl mungkin tidak berfungsi pada paparan kedua."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Apl tidak menyokong pelancaran pada paparan kedua."</string> <string name="accessibility_divider" msgid="703810061635792791">"Pembahagi skrin pisah"</string> + <string name="divider_title" msgid="5482989479865361192">"Pembahagi skrin pisah"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Skrin penuh kiri"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kiri 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Gelembung"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Urus"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Gelembung diketepikan."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Ketik untuk memulakan semula apl ini dan menggunakan skrin penuh."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Ketik untuk memulakan semula apl ini untuk mendapatkan paparan yang lebih baik."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Isu kamera?\nKetik untuk memuatkan semula"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Isu tidak dibetulkan?\nKetik untuk kembali"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tiada isu kamera? Ketik untuk mengetepikan."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Sesetengah apl berfungsi paling baik dalam mod potret"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Cuba salah satu daripada pilihan ini untuk memanfaatkan ruang anda sepenuhnya"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Putar peranti anda untuk beralih ke skrin penuh"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ketik dua kali bersebelahan apl untuk menempatkan semula apl"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Seret apl lain untuk skrin pisah"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketik dua kali di luar apl untuk menempatkan semula apl itu"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimumkan"</string> + <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string> + <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string> + <string name="handle_text" msgid="1766582106752184456">"Pemegang"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Skrin penuh"</string> + <string name="desktop_text" msgid="1077633567027630454">"Mod Desktop"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Skrin Pisah"</string> + <string name="more_button_text" msgid="3655388105592893530">"Lagi"</string> + <string name="float_button_text" msgid="9221657008391364581">"Terapung"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml index 08642c47c91a..afea48d7b510 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Gambar dalam Gambar"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tiada tajuk)"</string> - <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Tutup"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Skrin penuh"</string> - <string name="pip_move" msgid="1544227837964635439">"Alihkan PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Kembangkan PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Kuncupkan PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Tekan dua kali "<annotation icon="home_icon">" LAMAN UTAMA "</annotation>" untuk mengakses kawalan"</string> + <string name="pip_move" msgid="158770205886688553">"Alih"</string> + <string name="pip_expand" msgid="1051966011679297308">"Kembangkan"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Kuncupkan"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Tekan dua kali "<annotation icon="home_icon">"LAMAN UTAMA"</annotation>" untuk mengakses kawalan"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu Gambar dalam Gambar."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Alih ke kiri"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Alih ke kanan"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Alih ke atas"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Alih ke bawah"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Selesai"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index 15d182c6451e..b2bb37dd7730 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"ဆက်တင်များ"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းသို့ ဝင်ရန်"</string> <string name="pip_menu_title" msgid="5393619322111827096">"မီနူး"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"နှစ်ခုထပ်၍ ကြည့်ခြင်းမီနူး"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> သည် နှစ်ခုထပ်၍ကြည့်ခြင်း ဖွင့်ထားသည်"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> အား ဤဝန်ဆောင်မှုကို အသုံးမပြုစေလိုလျှင် ဆက်တင်ကိုဖွင့်ရန် တို့ပြီး ၎င်းဝန်ဆောင်မှုကို ပိတ်လိုက်ပါ။"</string> <string name="pip_play" msgid="3496151081459417097">"ဖွင့်ရန်"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ။"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"အက်ပ်သည် မျက်နှာပြင်ခွဲပြရန် ပံ့ပိုးထားခြင်းမရှိပါ။"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်။"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string> <string name="accessibility_divider" msgid="703810061635792791">"မျက်နှာပြင်ခွဲခြမ်း ပိုင်းခြားပေးသည့်စနစ်"</string> + <string name="divider_title" msgid="5482989479865361192">"မျက်နှာပြင်ခွဲ၍ပြသသည့် စနစ်"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ဘယ်ဘက် မျက်နှာပြင်အပြည့်"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ဘယ်ဘက်မျက်နှာပြင် ၇၀%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ဘယ်ဘက် မျက်နှာပြင် ၅၀%"</string> @@ -66,19 +69,29 @@ <string name="bubbles_user_education_description" msgid="4215862563054175407">"စကားဝိုင်းအသစ်များကို မျောနေသည့် သင်္ကေတများ သို့မဟုတ် ပူဖောင်းကွက်များအဖြစ် မြင်ရပါမည်။ ပူဖောင်းကွက်ကိုဖွင့်ရန် တို့ပါ။ ရွှေ့ရန် ၎င်းကို ဖိဆွဲပါ။"</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"ပူဖောင်းကွက်ကို အချိန်မရွေး ထိန်းချုပ်ရန်"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"ဤအက်ပ်မှနေ၍ ပူဖောင်းများကို ပိတ်ရန်အတွက် \'စီမံရန်\' ကို တို့ပါ"</string> - <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ရပြီ"</string> + <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"နားလည်ပြီ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"လတ်တလော ပူဖောင်းကွက်များ မရှိပါ"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"လတ်တလော ပူဖောင်းကွက်များနှင့် ပိတ်လိုက်သော ပူဖောင်းကွက်များကို ဤနေရာတွင် မြင်ရပါမည်"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"ပူဖောင်းဖောက်သံ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"စီမံရန်"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ပူဖောင်းကွက် ဖယ်လိုက်သည်။"</string> - <string name="restart_button_description" msgid="5887656107651190519">"ဤအက်ပ်ကို ပြန်စပြီး ဖန်သားပြင်အပြည့်လုပ်ရန် တို့ပါ။"</string> + <string name="restart_button_description" msgid="6712141648865547958">"ပိုကောင်းသောမြင်ကွင်းအတွက် ဤအက်ပ်ပြန်စရန် တို့နိုင်သည်။"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"အချို့အက်ပ်များသည် ဒေါင်လိုက်တွင် အကောင်းဆုံးလုပ်ဆောင်သည်"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"သင့်နေရာကို အကောင်းဆုံးအသုံးပြုနိုင်ရန် ဤရွေးစရာများထဲမှ တစ်ခုကို စမ်းကြည့်ပါ"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ဖန်သားပြင်အပြည့်လုပ်ရန် သင့်စက်ကို လှည့်နိုင်သည်"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"အက်ပ်နေရာပြန်ချရန် ၎င်းဘေးတွင် နှစ်ချက်တို့နိုင်သည်"</string> - <string name="letterbox_education_got_it" msgid="4057634570866051177">"ရပြီ"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"မျက်နှာပြင် ခွဲ၍ပြသနိုင်ရန် နောက်အက်ပ်တစ်ခုကို ဖိဆွဲပါ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string> + <string name="letterbox_education_got_it" msgid="4057634570866051177">"နားလည်ပြီ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string> + <string name="minimize_button_text" msgid="271592547935841753">"ချုံ့ရန်"</string> + <string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string> + <string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string> + <string name="handle_text" msgid="1766582106752184456">"သုံးသူအမည်"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"ဖန်သားပြင်အပြည့်"</string> + <string name="desktop_text" msgid="1077633567027630454">"ဒက်စ်တော့မုဒ်"</string> + <string name="split_screen_text" msgid="1396336058129570886">"မျက်နှာပြင် ခွဲ၍ပြသရန်"</string> + <string name="more_button_text" msgid="3655388105592893530">"ပိုပြပါ"</string> + <string name="float_button_text" msgid="9221657008391364581">"မျှောရန်"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-my/strings_tv.xml b/libs/WindowManager/Shell/res/values-my/strings_tv.xml index e01daee115ca..f3ed65da43da 100644 --- a/libs/WindowManager/Shell/res/values-my/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-my/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"နှစ်ခုထပ်၍ကြည့်ခြင်း"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ခေါင်းစဉ်မဲ့ အစီအစဉ်)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP ကိုပိတ်ပါ"</string> + <string name="pip_close" msgid="2955969519031223530">"ပိတ်ရန်"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"မျက်နှာပြင် အပြည့်"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP ရွှေ့ရန်"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP ကို ချဲ့ရန်"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP ကို လျှော့ပြပါ"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" ထိန်းချုပ်မှုအတွက် "<annotation icon="home_icon">" ပင်မခလုတ် "</annotation>" နှစ်ချက်နှိပ်ပါ"</string> + <string name="pip_move" msgid="158770205886688553">"ရွှေ့ရန်"</string> + <string name="pip_expand" msgid="1051966011679297308">"ချဲ့ရန်"</string> + <string name="pip_collapse" msgid="3903295106641385962">"လျှော့ပြရန်"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"ထိန်းချုပ်မှုအတွက် "<annotation icon="home_icon">" ပင်မခလုတ် "</annotation>" ကို နှစ်ချက်နှိပ်ပါ"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"နှစ်ခုထပ်၍ ကြည့်ခြင်းမီနူး။"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ဘယ်သို့ရွှေ့ရန်"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ညာသို့ရွှေ့ရန်"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"အပေါ်သို့ရွှေ့ရန်"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"အောက်သို့ရွှေ့ရန်"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ပြီးပြီ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 9fd42b2f129c..90b9dfca6116 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Innstillinger"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Aktivér delt skjerm"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Meny"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Bilde-i-bilde-meny"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> er i bilde-i-bilde"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Hvis du ikke vil at <xliff:g id="NAME">%s</xliff:g> skal bruke denne funksjonen, kan du trykke for å åpne innstillingene og slå den av."</string> <string name="pip_play" msgid="3496151081459417097">"Spill av"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Avslutt oppbevaring"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Det kan hende at appen ikke fungerer med delt skjerm."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen støtter ikke delt skjerm."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne appen kan bare åpnes i ett vindu."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer kanskje ikke på en sekundær skjerm."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke kjøres på sekundære skjermer."</string> <string name="accessibility_divider" msgid="703810061635792791">"Skilleelement for delt skjerm"</string> + <string name="divider_title" msgid="5482989479865361192">"Skilleelement for delt skjerm"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Utvid den venstre delen av skjermen til hele skjermen"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Sett størrelsen på den venstre delen av skjermen til 70 %"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sett størrelsen på den venstre delen av skjermen til 50 %"</string> @@ -63,8 +66,8 @@ <string name="bubble_dismiss_text" msgid="8816558050659478158">"Lukk boblen"</string> <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ikke vis samtaler i bobler"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat med bobler"</string> - <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som flytende ikoner eller bobler. Trykk for å åpne bobler. Dra for å flytte dem."</string> - <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontrollér bobler når som helst"</string> + <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som flytende ikoner eller bobler. Trykk for å åpne en boble. Dra for å flytte den."</string> + <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontroller bobler når som helst"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Trykk på Administrer for å slå av bobler for denne appen"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Greit"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen nylige bobler"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Boble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen er avvist."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Trykk for å starte denne appen på nytt og vise den i fullskjerm."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Trykk for å starte denne appen på nytt for bedre visning."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du kameraproblemer?\nTrykk for å tilpasse"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ble ikke problemet løst?\nTrykk for å gå tilbake"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen kameraproblemer? Trykk for å lukke."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Noen apper fungerer best i stående format"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prøv et av disse alternativene for å få mest mulig ut av plassen din"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Roter enheten for å starte fullskjerm"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dobbelttrykk ved siden av en app for å flytte den"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gjør mer"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dra inn en annen app for å bruke delt skjerm"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dobbelttrykk utenfor en app for å flytte den"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string> + <string name="close_button_text" msgid="2913281996024033299">"Lukk"</string> + <string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string> + <string name="handle_text" msgid="1766582106752184456">"Håndtak"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Fullskjerm"</string> + <string name="desktop_text" msgid="1077633567027630454">"Skrivebordmodus"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Delt skjerm"</string> + <string name="more_button_text" msgid="3655388105592893530">"Mer"</string> + <string name="float_button_text" msgid="9221657008391364581">"Svevende"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml index 65ed0b7f5bff..1402e3c9c9bc 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Bilde-i-bilde"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uten tittel)"</string> - <string name="pip_close" msgid="9135220303720555525">"Lukk PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Lukk"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Fullskjerm"</string> - <string name="pip_move" msgid="1544227837964635439">"Flytt BIB"</string> - <string name="pip_expand" msgid="7605396312689038178">"Vis BIB"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Skjul BIB"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Dobbelttrykk på "<annotation icon="home_icon">"HJEM"</annotation>" for å åpne kontroller"</string> + <string name="pip_move" msgid="158770205886688553">"Flytt"</string> + <string name="pip_expand" msgid="1051966011679297308">"Vis"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Skjul"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Dobbelttrykk på "<annotation icon="home_icon">"HJEM"</annotation>" for å åpne kontrollene"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Bilde-i-bilde-meny."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Flytt til venstre"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Flytt til høyre"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Flytt opp"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Flytt ned"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Ferdig"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index 8dfec88cc29d..15f22f7ff2d3 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"सेटिङहरू"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रिन मोड प्रयोग गर्नुहोस्"</string> <string name="pip_menu_title" msgid="5393619322111827096">"मेनु"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"\"picture-in-picture\" मेनु"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> Picture-in-picture मा छ"</string> <string name="pip_notification_message" msgid="8854051911700302620">"तपाईं <xliff:g id="NAME">%s</xliff:g> ले सुविधा प्रयोग नगरोस् भन्ने चाहनुहुन्छ भने ट्याप गरेर सेटिङहरू खोल्नुहोस् र यसलाई निष्क्रिय पार्नुहोस्।"</string> <string name="pip_play" msgid="3496151081459417097">"प्ले गर्नुहोस्"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्ट्यास गर्नुहोस्"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"एप विभाजित स्क्रिनमा काम नगर्न सक्छ।"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अनुप्रयोगले विभाजित-स्क्रिनलाई समर्थन गर्दैन।"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"यो एप एउटा विन्डोमा मात्र खोल्न मिल्छ।"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"अनुप्रयोगले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"</string> <string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रिन छुट्याउने"</string> + <string name="divider_title" msgid="5482989479865361192">"स्प्लिट स्क्रिन डिभाइडर"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"बायाँ भाग फुल स्क्रिन"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"बायाँ भाग ७०%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"बायाँ भाग ५०%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापन गर्नुहोस्"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल हटाइयो।"</string> - <string name="restart_button_description" msgid="5887656107651190519">"यो एप रिस्टार्ट गर्न ट्याप गर्नुहोस् र फुल स्क्रिन मोडमा जानुहोस्।"</string> + <string name="restart_button_description" msgid="6712141648865547958">"यो एप अझ राम्रो हेर्न मिल्ने बनाउनका लागि यसलाई रिस्टार्ट गर्न ट्याप गर्नुहोस्।"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"केही एपहरूले पोर्ट्रेटमा राम्रोसँग काम गर्छन्"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"तपाईं स्क्रिनको अधिकतम ठाउँ प्रयोग गर्न चाहनुहुन्छ भने यीमध्ये कुनै विकल्प प्रयोग गरी हेर्नुहोस्"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"तपाईं फुल स्क्रिन मोड हेर्न चाहनुहुन्छ भने आफ्नो डिभाइस रोटेट गर्नुहोस्"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको छेउमा डबल ट्याप गर्नुहोस्"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"थप कुरा हेर्नुहोस् र गर्नुहोस्"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको बाहिर डबल ट्याप गर्नुहोस्"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"बुझेँ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"थप जानकारी प्राप्त गर्न चाहनुहुन्छ भने एक्स्पान्ड गर्नुहोस्।"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string> + <string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string> + <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string> + <string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string> + <string name="handle_text" msgid="1766582106752184456">"ह्यान्डल"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"फुल स्क्रिन"</string> + <string name="desktop_text" msgid="1077633567027630454">"डेस्कटप मोड"</string> + <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रिन"</string> + <string name="more_button_text" msgid="3655388105592893530">"थप"</string> + <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml index d33fed67efb6..2b1f20fba4e2 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षकविहीन कार्यक्रम)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP लाई बन्द गर्नुहोस्"</string> + <string name="pip_close" msgid="2955969519031223530">"बन्द गर्नुहोस्"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रिन"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP सार्नुहोस्"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP विन्डो एक्स्पान्ड गर्नु…"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP विन्डो कोल्याप्स गर्नुहोस्"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" कन्ट्रोल मेनु खोल्न "<annotation icon="home_icon">" होम "</annotation>" बटन दुई पटक थिच्नुहोस्"</string> + <string name="pip_move" msgid="158770205886688553">"सार्नुहोस्"</string> + <string name="pip_expand" msgid="1051966011679297308">"एक्स्पान्ड गर्नुहोस्"</string> + <string name="pip_collapse" msgid="3903295106641385962">"कोल्याप्स गर्नुहोस्"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"कन्ट्रोल मेनु खोल्न "<annotation icon="home_icon">" होम "</annotation>" बटन दुई पटक थिच्नुहोस्"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"\"picture-in-picture\" मेनु।"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"बायाँतिर सार्नुहोस्"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"दायाँतिर सार्नुहोस्"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"माथितिर सार्नुहोस्"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"तलतिर सार्नुहोस्"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"सम्पन्न भयो"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 8468b04c66da..f9f4ef4a4b63 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Instellingen"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Gesplitst scherm openen"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Scherm-in-scherm-menu"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in scherm-in-scherm"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Als je niet wilt dat <xliff:g id="NAME">%s</xliff:g> deze functie gebruikt, tik je om de instellingen te openen en zet je de functie uit."</string> <string name="pip_play" msgid="3496151081459417097">"Afspelen"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Niet meer verbergen"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"De app werkt mogelijk niet met gesplitst scherm."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App biedt geen ondersteuning voor gesplitst scherm."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Deze app kan slechts in 1 venster worden geopend."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App werkt mogelijk niet op een secundair scherm."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App kan niet op secundaire displays worden gestart."</string> <string name="accessibility_divider" msgid="703810061635792791">"Scheiding voor gesplitst scherm"</string> + <string name="divider_title" msgid="5482989479865361192">"Scheiding voor gesplitst scherm"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Linkerscherm op volledig scherm"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Linkerscherm 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Linkerscherm 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubbel"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubbel gesloten."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tik om deze app opnieuw te starten en te openen op het volledige scherm."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tik om deze app opnieuw op te starten voor een betere weergave."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Cameraproblemen?\nTik om opnieuw passend te maken."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Is dit geen oplossing?\nTik om terug te zetten."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen cameraproblemen? Tik om te sluiten."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Sommige apps werken het best in de staande stand"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Probeer een van deze opties om optimaal gebruik te maken van je ruimte"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Draai je apparaat om naar volledig scherm te schakelen"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dubbeltik naast een app om deze opnieuw te positioneren"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zie en doe meer"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Sleep een andere app hier naartoe om het scherm te splitsen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik naast een app om deze opnieuw te positioneren"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Uitvouwen voor meer informatie."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimaliseren"</string> + <string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string> + <string name="back_button_text" msgid="1469718707134137085">"Terug"</string> + <string name="handle_text" msgid="1766582106752184456">"Gebruikersnaam"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Volledig scherm"</string> + <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Gesplitst scherm"</string> + <string name="more_button_text" msgid="3655388105592893530">"Meer"</string> + <string name="float_button_text" msgid="9221657008391364581">"Zwevend"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml index 9763c5665ab2..6766773fa866 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Scherm-in-scherm"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Naamloos programma)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP sluiten"</string> + <string name="pip_close" msgid="2955969519031223530">"Sluiten"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Volledig scherm"</string> - <string name="pip_move" msgid="1544227837964635439">"SIS verplaatsen"</string> - <string name="pip_expand" msgid="7605396312689038178">"SIS uitvouwen"</string> - <string name="pip_collapse" msgid="5732233773786896094">"SIS samenvouwen"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Druk twee keer op "<annotation icon="home_icon">" HOME "</annotation>" voor bedieningselementen"</string> + <string name="pip_move" msgid="158770205886688553">"Verplaatsen"</string> + <string name="pip_expand" msgid="1051966011679297308">"Uitvouwen"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Samenvouwen"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Druk 2 keer op "<annotation icon="home_icon">"HOME"</annotation>" voor bedieningsopties"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Scherm-in-scherm-menu."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Naar links verplaatsen"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Naar rechts verplaatsen"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Omhoog verplaatsen"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Omlaag verplaatsen"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Klaar"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index a8d8448edf99..5a76a6f3f22a 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"ସେଟିଂସ୍"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ମୋଡ ବ୍ୟବହାର କରନ୍ତୁ"</string> <string name="pip_menu_title" msgid="5393619322111827096">"ମେନୁ"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ପିକଚର-ଇନ-ପିକଚର ମେନୁ"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"ଛବି-ଭିତରେ-ଛବି\"ରେ ଅଛି"</string> <string name="pip_notification_message" msgid="8854051911700302620">"ଏହି ବୈଶିଷ୍ଟ୍ୟ <xliff:g id="NAME">%s</xliff:g> ବ୍ୟବହାର ନକରିବାକୁ ଯଦି ଆପଣ ଚାହାଁନ୍ତି, ସେଟିଙ୍ଗ ଖୋଲିବାକୁ ଟାପ୍ କରନ୍ତୁ ଏବଂ ଏହା ଅଫ୍ କରିଦିଅନ୍ତୁ।"</string> <string name="pip_play" msgid="3496151081459417097">"ପ୍ଲେ କରନ୍ତୁ"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ଦେଖାନ୍ତୁ"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରିନରେ ଆପ୍ କାମ କରିନପାରେ।"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ଆପ୍ ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରୀନକୁ ସପୋର୍ଟ କରେ ନାହିଁ।"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ।"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍ କାମ ନକରିପାରେ।"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍ ଲଞ୍ଚ ସପୋର୍ଟ କରେ ନାହିଁ।"</string> <string name="accessibility_divider" msgid="703810061635792791">"ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରୀନ ବିଭାଜକ"</string> + <string name="divider_title" msgid="5482989479865361192">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ଡିଭାଇଡର"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ବାମ ପଟକୁ ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍ କରନ୍ତୁ"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ବାମ ପଟକୁ 70% କରନ୍ତୁ"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ବାମ ପଟକୁ 50% କରନ୍ତୁ"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ବବଲ୍"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ପରିଚାଳନା କରନ୍ତୁ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ବବଲ୍ ଖାରଜ କରାଯାଇଛି।"</string> - <string name="restart_button_description" msgid="5887656107651190519">"ଏହି ଆପକୁ ରିଷ୍ଟାର୍ଟ କରି ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ।"</string> + <string name="restart_button_description" msgid="6712141648865547958">"ଏକ ଆହୁରି ଭଲ ଭ୍ୟୁ ପାଇଁ ଏହି ଆପ ରିଷ୍ଟାର୍ଟ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"କିଛି ଆପ ପୋର୍ଟ୍ରେଟରେ ସବୁଠାରୁ ଭଲ କାମ କରେ"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ଆପଣଙ୍କ ସ୍ପେସରୁ ଅଧିକ ଲାଭ ପାଇବାକୁ ଏହି ବିକଳ୍ପଗୁଡ଼ିକ ମଧ୍ୟରୁ ଗୋଟିଏ ବ୍ୟବହାର କରି ଦେଖନ୍ତୁ"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ପୂର୍ଣ୍ଣ-ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ରୋଟେଟ କରନ୍ତୁ"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହା ପାଖରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ଦେଖନ୍ତୁ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରନ୍ତୁ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହାର ବାହାରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ବୁଝିଗଲି"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string> + <string name="minimize_button_text" msgid="271592547935841753">"ଛୋଟ କରନ୍ତୁ"</string> + <string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string> + <string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string> + <string name="handle_text" msgid="1766582106752184456">"ହେଣ୍ଡେଲ"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"ପୂର୍ଣ୍ଣସ୍କ୍ରିନ"</string> + <string name="desktop_text" msgid="1077633567027630454">"ଡେସ୍କଟପ ମୋଡ"</string> + <string name="split_screen_text" msgid="1396336058129570886">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ"</string> + <string name="more_button_text" msgid="3655388105592893530">"ଅଧିକ"</string> + <string name="float_button_text" msgid="9221657008391364581">"ଫ୍ଲୋଟ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-or/strings_tv.xml b/libs/WindowManager/Shell/res/values-or/strings_tv.xml index e0344855bd1f..1e81f4d80f32 100644 --- a/libs/WindowManager/Shell/res/values-or/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-or/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ପିକଚର୍-ଇନ୍-ପିକଚର୍"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(କୌଣସି ଟାଇଟଲ୍ ପ୍ରୋଗ୍ରାମ୍ ନାହିଁ)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP ବନ୍ଦ କରନ୍ତୁ"</string> + <string name="pip_close" msgid="2955969519031223530">"ବନ୍ଦ କରନ୍ତୁ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍"</string> - <string name="pip_move" msgid="1544227837964635439">"PIPକୁ ମୁଭ କରନ୍ତୁ"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIPକୁ ବିସ୍ତାର କରନ୍ତୁ"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIPକୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ପାଇଁ "<annotation icon="home_icon">" ହୋମ ବଟନ "</annotation>"କୁ ଦୁଇଥର ଦବାନ୍ତୁ"</string> + <string name="pip_move" msgid="158770205886688553">"ମୁଭ କରନ୍ତୁ"</string> + <string name="pip_expand" msgid="1051966011679297308">"ବିସ୍ତାର କରନ୍ତୁ"</string> + <string name="pip_collapse" msgid="3903295106641385962">"ସଙ୍କୁଚିତ କରନ୍ତୁ"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ପାଇଁ "<annotation icon="home_icon">"ହୋମ ବଟନ"</annotation>"କୁ ଦୁଇଥର ଦବାନ୍ତୁ"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ପିକଚର-ଇନ-ପିକଚର ମେନୁ।"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ବାମକୁ ମୁଭ କରନ୍ତୁ"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ଉପରକୁ ମୁଭ କରନ୍ତୁ"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ତଳକୁ ମୁଭ କରନ୍ତୁ"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ହୋଇଗଲା"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index f99176cb682d..617c95eec8d9 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"ਸੈਟਿੰਗਾਂ"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿੱਚ ਦਾਖਲ ਹੋਵੋ"</string> <string name="pip_menu_title" msgid="5393619322111827096">"ਮੀਨੂ"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ਤਸਵੀਰ-ਵਿੱਚ-ਤਸਵੀਰ ਮੀਨੂ"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ਤਸਵੀਰ-ਅੰਦਰ-ਤਸਵੀਰ ਵਿੱਚ ਹੈ"</string> <string name="pip_notification_message" msgid="8854051911700302620">"ਜੇਕਰ ਤੁਸੀਂ ਨਹੀਂ ਚਾਹੁੰਦੇ ਕਿ <xliff:g id="NAME">%s</xliff:g> ਐਪ ਇਸ ਵਿਸ਼ੇਸ਼ਤਾ ਦੀ ਵਰਤੋਂ ਕਰੇ, ਤਾਂ ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ ਅਤੇ ਇਸਨੂੰ ਬੰਦ ਕਰੋ।"</string> <string name="pip_play" msgid="3496151081459417097">"ਚਲਾਓ"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ਅਣਸਟੈਸ਼"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ।"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨੂੰ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ।"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ।"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇਆਂ \'ਤੇ ਲਾਂਚ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string> <string name="accessibility_divider" msgid="703810061635792791">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਡਿਵਾਈਡਰ"</string> + <string name="divider_title" msgid="5482989479865361192">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ਖੱਬੇ ਪੂਰੀ ਸਕ੍ਰੀਨ"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ਖੱਬੇ 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ਖੱਬੇ 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ਬੁਲਬੁਲਾ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕੀਤਾ ਗਿਆ।"</string> - <string name="restart_button_description" msgid="5887656107651190519">"ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ ਅਤੇ ਪੂਰੀ ਸਕ੍ਰੀਨ ਮੋਡ \'ਤੇ ਜਾਓ।"</string> + <string name="restart_button_description" msgid="6712141648865547958">"ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਲਈ ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"ਕੁਝ ਐਪਾਂ ਪੋਰਟਰੇਟ ਵਿੱਚ ਬਿਹਤਰ ਕੰਮ ਕਰਦੀਆਂ ਹਨ"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ਆਪਣੀ ਜਗ੍ਹਾ ਦਾ ਵੱਧ ਤੋਂ ਵੱਧ ਲਾਹਾ ਲੈਣ ਲਈ ਇਨ੍ਹਾਂ ਵਿਕਲਪਾਂ ਵਿੱਚੋਂ ਕੋਈ ਇੱਕ ਵਰਤ ਕੇ ਦੇਖੋ"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ਪੂਰੀ-ਸਕ੍ਰੀਨ ਮੋਡ \'ਤੇ ਜਾਣ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁਮਾਓ"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਅੱਗੇ ਡਬਲ ਟੈਪ ਕਰੋ"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ਦੇਖੋ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਕਰੋ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਬਾਹਰ ਡਬਲ ਟੈਪ ਕਰੋ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ਸਮਝ ਲਿਆ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string> + <string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string> + <string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string> + <string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string> + <string name="handle_text" msgid="1766582106752184456">"ਹੈਂਡਲ"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"ਪੂਰੀ-ਸਕ੍ਰੀਨ"</string> + <string name="desktop_text" msgid="1077633567027630454">"ਡੈਸਕਟਾਪ ਮੋਡ"</string> + <string name="split_screen_text" msgid="1396336058129570886">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string> + <string name="more_button_text" msgid="3655388105592893530">"ਹੋਰ"</string> + <string name="float_button_text" msgid="9221657008391364581">"ਫ਼ਲੋਟ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml index 9c01ac3f3cc0..758aafad457a 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ਤਸਵੀਰ-ਵਿੱਚ-ਤਸਵੀਰ"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ਸਿਰਲੇਖ-ਰਹਿਤ ਪ੍ਰੋਗਰਾਮ)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP ਬੰਦ ਕਰੋ"</string> + <string name="pip_close" msgid="2955969519031223530">"ਬੰਦ ਕਰੋ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP ਨੂੰ ਲਿਜਾਓ"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP ਨੂੰ ਸਮੇਟੋ"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" ਕੰਟਰੋਲਾਂ ਲਈ "<annotation icon="home_icon">" ਹੋਮ ਬਟਨ "</annotation>" ਨੂੰ ਦੋ ਵਾਰ ਦਬਾਓ"</string> + <string name="pip_move" msgid="158770205886688553">"ਲਿਜਾਓ"</string> + <string name="pip_expand" msgid="1051966011679297308">"ਵਿਸਤਾਰ ਕਰੋ"</string> + <string name="pip_collapse" msgid="3903295106641385962">"ਸਮੇਟੋ"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"ਕੰਟਰੋਲਾਂ ਲਈ "<annotation icon="home_icon">"ਹੋਮ"</annotation>" ਨੂੰ ਦੋ ਵਾਰ ਦਬਾਓ"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ਤਸਵੀਰ-ਵਿੱਚ-ਤਸਵੀਰ ਮੀਨੂ।"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ਖੱਬੇ ਲਿਜਾਓ"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ਸੱਜੇ ਲਿਜਾਓ"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ਉੱਪਰ ਲਿਜਾਓ"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ਹੇਠਾਂ ਲਿਜਾਓ"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ਹੋ ਗਿਆ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index f2147c04d335..4a17ec74bada 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Ustawienia"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Włącz podzielony ekran"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu funkcji Obraz w obrazie."</string> <string name="pip_notification_title" msgid="1347104727641353453">"Aplikacja <xliff:g id="NAME">%s</xliff:g> działa w trybie obraz w obrazie"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Jeśli nie chcesz, by aplikacja <xliff:g id="NAME">%s</xliff:g> korzystała z tej funkcji, otwórz ustawienia i wyłącz ją."</string> <string name="pip_play" msgid="3496151081459417097">"Odtwórz"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zabierz ze schowka"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacja może nie działać przy podzielonym ekranie."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacja nie obsługuje dzielonego ekranu."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ta aplikacja może być otwarta tylko w 1 oknie."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacja może nie działać na dodatkowym ekranie."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacja nie obsługuje uruchamiania na dodatkowych ekranach."</string> <string name="accessibility_divider" msgid="703810061635792791">"Linia dzielenia ekranu"</string> + <string name="divider_title" msgid="5482989479865361192">"Linia dzielenia ekranu"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lewa część ekranu na pełnym ekranie"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% lewej części ekranu"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% lewej części ekranu"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Dymek"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Zarządzaj"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Zamknięto dymek"</string> - <string name="restart_button_description" msgid="5887656107651190519">"Kliknij, by uruchomić tę aplikację ponownie i przejść w tryb pełnoekranowy."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Kliknij, aby zrestartować aplikację i zyskać lepszą widoczność."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemy z aparatem?\nKliknij, aby dopasować"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Naprawa się nie udała?\nKliknij, aby cofnąć"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Brak problemów z aparatem? Kliknij, aby zamknąć"</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Niektóre aplikacje działają najlepiej w orientacji pionowej"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Wypróbuj jedną z tych opcji, aby jak najlepiej wykorzystać miejsce"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Obróć urządzenie, aby przejść do pełnego ekranu"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Kliknij dwukrotnie obok aplikacji, aby ją przenieść"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobacz i zrób więcej"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Przeciągnij drugą aplikację, aby podzielić ekran"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kliknij dwukrotnie poza aplikacją, aby ją przenieść"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimalizuj"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string> + <string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string> + <string name="handle_text" msgid="1766582106752184456">"Uchwyt"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Pełny ekran"</string> + <string name="desktop_text" msgid="1077633567027630454">"Tryb pulpitu"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Podzielony ekran"</string> + <string name="more_button_text" msgid="3655388105592893530">"Więcej"</string> + <string name="float_button_text" msgid="9221657008391364581">"Pływające"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml index b922e2d5a6ba..b598351b5127 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Obraz w obrazie"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez tytułu)"</string> - <string name="pip_close" msgid="9135220303720555525">"Zamknij PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Zamknij"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pełny ekran"</string> - <string name="pip_move" msgid="1544227837964635439">"Przenieś PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Rozwiń PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Zwiń PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Naciśnij dwukrotnie "<annotation icon="home_icon">"EKRAN GŁÓWNY"</annotation>", aby wyświetlić ustawienia"</string> + <string name="pip_move" msgid="158770205886688553">"Przenieś"</string> + <string name="pip_expand" msgid="1051966011679297308">"Rozwiń"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Zwiń"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Naciśnij dwukrotnie "<annotation icon="home_icon">"EKRAN GŁÓWNY"</annotation>", aby wyświetlić ustawienia"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu funkcji Obraz w obrazie."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Przenieś w lewo"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Przenieś w prawo"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Przenieś w górę"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Przenieś w dół"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gotowe"</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 2efc5543dd87..69be68ead461 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Configurações"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Dividir tela"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu do picture-in-picture"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está em picture-in-picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Se você não quer que o app <xliff:g id="NAME">%s</xliff:g> use este recurso, toque para abrir as configurações e desativá-lo."</string> <string name="pip_play" msgid="3496151081459417097">"Reproduzir"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string> <string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string> + <string name="divider_title" msgid="5482989479865361192">"Divisor de tela"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lado esquerdo em tela cheia"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Esquerda a 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bolha"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar o app e usar tela cheia."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar o app e atualizar a visualização."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Alguns apps funcionam melhor em modo retrato"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Tente uma destas opções para aproveitar seu espaço ao máximo"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gire o dispositivo para entrar no modo de tela cheia"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes ao lado de um app para reposicionar"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outro app para a tela dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> + <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string> + <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string> + <string name="handle_text" msgid="1766582106752184456">"Alça"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string> + <string name="desktop_text" msgid="1077633567027630454">"Modo área de trabalho"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string> + <string name="more_button_text" msgid="3655388105592893530">"Mais"</string> + <string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml index cc4eb3c32c1f..2528ea9b8ebb 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string> - <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Fechar"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string> - <string name="pip_move" msgid="1544227837964635439">"Mover picture-in-picture"</string> - <string name="pip_expand" msgid="7605396312689038178">"Abrir picture-in-picture"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Fechar picture-in-picture"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Pressione o botão "<annotation icon="home_icon">"home"</annotation>" duas vezes para acessar os controles"</string> + <string name="pip_move" msgid="158770205886688553">"Mover"</string> + <string name="pip_expand" msgid="1051966011679297308">"Abrir"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Fechar"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Pressione o botão "<annotation icon="home_icon">"HOME"</annotation>" duas vezes para acessar os controles"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu do picture-in-picture"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover para a esquerda"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover para a direita"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover para cima"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover para baixo"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Concluído"</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 c68a6934dead..13e83ac0bcf7 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Definições"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Aceder ao ecrã dividido"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu de ecrã no ecrã"</string> <string name="pip_notification_title" msgid="1347104727641353453">"A app <xliff:g id="NAME">%s</xliff:g> está no modo de ecrã no ecrã"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Se não pretende que a app <xliff:g id="NAME">%s</xliff:g> utilize esta funcionalidade, toque para abrir as definições e desative-a."</string> <string name="pip_play" msgid="3496151081459417097">"Reproduzir"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Remover do armazenamento"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"A app pode não funcionar com o ecrã dividido."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A app não é compatível com o ecrã dividido."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app só pode ser aberta em 1 janela."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"A app pode não funcionar num ecrã secundário."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A app não é compatível com o início em ecrãs secundários."</string> <string name="accessibility_divider" msgid="703810061635792791">"Divisor do ecrã dividido"</string> + <string name="divider_title" msgid="5482989479865361192">"Divisor do ecrã dividido"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ecrã esquerdo inteiro"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% no ecrã esquerdo"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% no ecrã esquerdo"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Balão"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gerir"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão ignorado."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar esta app e ficar em ecrã inteiro."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar esta app e ficar com uma melhor visão."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmara?\nToque aqui para reajustar"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Não foi corrigido?\nToque para reverter"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nenhum problema com a câmara? Toque para ignorar."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Algumas apps funcionam melhor no modo vertical"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Experimente uma destas opções para aproveitar ao máximo o seu espaço"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rode o dispositivo para ficar em ecrã inteiro"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes junto a uma app para a reposicionar"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outra app para usar o ecrã dividido"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de uma app para a reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> + <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string> + <string name="back_button_text" msgid="1469718707134137085">"Anterior"</string> + <string name="handle_text" msgid="1766582106752184456">"Indicador"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Ecrã inteiro"</string> + <string name="desktop_text" msgid="1077633567027630454">"Modo de ambiente de trabalho"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Ecrã dividido"</string> + <string name="more_button_text" msgid="3655388105592893530">"Mais"</string> + <string name="float_button_text" msgid="9221657008391364581">"Flutuar"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml index c4ae78d89ba8..a678f581c272 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Ecrã no ecrã"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sem título do programa)"</string> - <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Fechar"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ecrã inteiro"</string> - <string name="pip_move" msgid="1544227837964635439">"Mover Ecrã no ecrã"</string> - <string name="pip_expand" msgid="7605396312689038178">"Expandir Ecrã no ecrã"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Reduzir Ecrã no ecrã"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Prima duas vezes "<annotation icon="home_icon">" PÁGINA INICIAL "</annotation>" para controlos"</string> + <string name="pip_move" msgid="158770205886688553">"Mover"</string> + <string name="pip_expand" msgid="1051966011679297308">"Expandir"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Reduzir"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Prima duas vezes "<annotation icon="home_icon">"PÁGINA INICIAL"</annotation>" para os controlos"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu de ecrã no ecrã."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover para a esquerda"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover para a direita"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover para cima"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover para baixo"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Concluído"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 2efc5543dd87..69be68ead461 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Configurações"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Dividir tela"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu do picture-in-picture"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está em picture-in-picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Se você não quer que o app <xliff:g id="NAME">%s</xliff:g> use este recurso, toque para abrir as configurações e desativá-lo."</string> <string name="pip_play" msgid="3496151081459417097">"Reproduzir"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string> <string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string> + <string name="divider_title" msgid="5482989479865361192">"Divisor de tela"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lado esquerdo em tela cheia"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Esquerda a 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bolha"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar o app e usar tela cheia."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar o app e atualizar a visualização."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Alguns apps funcionam melhor em modo retrato"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Tente uma destas opções para aproveitar seu espaço ao máximo"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gire o dispositivo para entrar no modo de tela cheia"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes ao lado de um app para reposicionar"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outro app para a tela dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> + <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string> + <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string> + <string name="handle_text" msgid="1766582106752184456">"Alça"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string> + <string name="desktop_text" msgid="1077633567027630454">"Modo área de trabalho"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string> + <string name="more_button_text" msgid="3655388105592893530">"Mais"</string> + <string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml index cc4eb3c32c1f..2528ea9b8ebb 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string> - <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Fechar"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string> - <string name="pip_move" msgid="1544227837964635439">"Mover picture-in-picture"</string> - <string name="pip_expand" msgid="7605396312689038178">"Abrir picture-in-picture"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Fechar picture-in-picture"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Pressione o botão "<annotation icon="home_icon">"home"</annotation>" duas vezes para acessar os controles"</string> + <string name="pip_move" msgid="158770205886688553">"Mover"</string> + <string name="pip_expand" msgid="1051966011679297308">"Abrir"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Fechar"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Pressione o botão "<annotation icon="home_icon">"HOME"</annotation>" duas vezes para acessar os controles"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu do picture-in-picture"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover para a esquerda"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover para a direita"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover para cima"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover para baixo"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Concluído"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index 804d34f980ff..c112a9d26a7c 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -17,25 +17,28 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="pip_phone_close" msgid="5783752637260411309">"Închideți"</string> - <string name="pip_phone_expand" msgid="2579292903468287504">"Extindeți"</string> + <string name="pip_phone_close" msgid="5783752637260411309">"Închide"</string> + <string name="pip_phone_expand" msgid="2579292903468287504">"Extinde"</string> <string name="pip_phone_settings" msgid="5468987116750491918">"Setări"</string> - <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesați ecranul împărțit"</string> + <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesează ecranul împărțit"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meniu picture-in-picture"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> este în modul picture-in-picture"</string> - <string name="pip_notification_message" msgid="8854051911700302620">"Dacă nu doriți ca <xliff:g id="NAME">%s</xliff:g> să utilizeze această funcție, atingeți pentru a deschide setările și dezactivați-o."</string> - <string name="pip_play" msgid="3496151081459417097">"Redați"</string> - <string name="pip_pause" msgid="690688849510295232">"Întrerupeți"</string> - <string name="pip_skip_to_next" msgid="8403429188794867653">"Treceți la următorul"</string> - <string name="pip_skip_to_prev" msgid="7172158111196394092">"Treceți la cel anterior"</string> - <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionați"</string> - <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stocați"</string> - <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulați stocarea"</string> + <string name="pip_notification_message" msgid="8854051911700302620">"Dacă nu vrei ca <xliff:g id="NAME">%s</xliff:g> să folosească această funcție, atinge pentru a deschide setările și dezactiveaz-o."</string> + <string name="pip_play" msgid="3496151081459417097">"Redă"</string> + <string name="pip_pause" msgid="690688849510295232">"Întrerupe"</string> + <string name="pip_skip_to_next" msgid="8403429188794867653">"Treci la următorul"</string> + <string name="pip_skip_to_prev" msgid="7172158111196394092">"Treci la cel anterior"</string> + <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionează"</string> + <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stochează"</string> + <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplicația nu acceptă ecranul împărțit."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplicația poate fi deschisă într-o singură fereastră."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplicația nu acceptă lansare pe ecrane secundare."</string> <string name="accessibility_divider" msgid="703810061635792791">"Separator pentru ecranul împărțit"</string> + <string name="divider_title" msgid="5482989479865361192">"Separator pentru ecranul împărțit"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Partea stângă pe ecran complet"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Partea stângă: 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Partea stângă: 50%"</string> @@ -47,38 +50,48 @@ <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Partea de sus: 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Partea de jos pe ecran complet"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Folosirea modului cu o mână"</string> - <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pentru a ieși, glisați în sus din partea de jos a ecranului sau atingeți oriunde deasupra ferestrei aplicației"</string> - <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Activați modul cu o mână"</string> - <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Părăsiți modul cu o mână"</string> + <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pentru a ieși, glisează în sus din partea de jos a ecranului sau atinge oriunde deasupra ferestrei aplicației"</string> + <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Activează modul cu o mână"</string> + <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Ieși din modul cu o mână"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Setări pentru baloanele <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Suplimentar"</string> - <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Adăugați înapoi în stivă"</string> + <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Adaugă înapoi în stivă"</string> <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de la <xliff:g id="APP_NAME">%2$s</xliff:g>"</string> <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de la <xliff:g id="APP_NAME">%2$s</xliff:g> și încă <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g>"</string> - <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mutați în stânga sus"</string> - <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mutați în dreapta sus"</string> - <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mutați în stânga jos"</string> - <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mutați în dreapta jos"</string> + <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mută în stânga sus"</string> + <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mută în dreapta sus"</string> + <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mută în stânga jos"</string> + <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mută în dreapta jos"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"Setări <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> - <string name="bubble_dismiss_text" msgid="8816558050659478158">"Închideți balonul"</string> - <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișați conversația în balon"</string> + <string name="bubble_dismiss_text" msgid="8816558050659478158">"Închide balonul"</string> + <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișa conversația în balon"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat cu baloane"</string> - <string name="bubbles_user_education_description" msgid="4215862563054175407">"Conversațiile noi apar ca pictograme flotante sau baloane. Atingeți pentru a deschide balonul. Trageți pentru a-l muta."</string> - <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controlați oricând baloanele"</string> - <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Atingeți Gestionați pentru a dezactiva baloanele din această aplicație"</string> + <string name="bubbles_user_education_description" msgid="4215862563054175407">"Conversațiile noi apar ca pictograme flotante sau baloane. Atinge pentru a deschide balonul. Trage pentru a-l muta."</string> + <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controlează oricând baloanele"</string> + <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Atinge Gestionează pentru a dezactiva baloanele din această aplicație"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nu există baloane recente"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Baloanele recente și baloanele respinse vor apărea aici"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string> - <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionați"</string> + <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionează"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balonul a fost respins."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Atingeți ca să reporniți aplicația și să treceți în modul ecran complet."</string> - <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Aveți probleme cu camera foto?\nAtingeți pentru a reîncadra"</string> - <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ați remediat problema?\nAtingeți pentru a reveni"</string> - <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu aveți probleme cu camera foto? Atingeți pentru a închide."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Unele aplicații funcționează cel mai bine în orientarea portret"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Încercați una dintre aceste opțiuni pentru a profita din plin de spațiu"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotiți dispozitivul pentru a trece în modul ecran complet"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Atingeți de două ori lângă o aplicație pentru a o repoziționa"</string> + <string name="restart_button_description" msgid="6712141648865547958">"Atinge ca să repornești aplicația pentru o vizualizare mai bună."</string> + <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ai probleme cu camera foto?\nAtinge pentru a reîncadra"</string> + <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ai remediat problema?\nAtinge pentru a reveni"</string> + <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu ai probleme cu camera foto? Atinge pentru a închide."</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vezi și fă mai multe"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Trage în altă aplicație pentru a folosi ecranul împărțit"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Atinge de două ori lângă o aplicație pentru a o repoziționa"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extinde pentru mai multe informații"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizează"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimizează"</string> + <string name="close_button_text" msgid="2913281996024033299">"Închide"</string> + <string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string> + <string name="handle_text" msgid="1766582106752184456">"Ghidaj"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Ecran complet"</string> + <string name="desktop_text" msgid="1077633567027630454">"Modul desktop"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Ecran împărțit"</string> + <string name="more_button_text" msgid="3655388105592893530">"Mai multe"</string> + <string name="float_button_text" msgid="9221657008391364581">"Flotantă"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml index 86a30f49df15..c3226ff28934 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program fără titlu)"</string> - <string name="pip_close" msgid="9135220303720555525">"Închideți PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Închide"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ecran complet"</string> - <string name="pip_move" msgid="1544227837964635439">"Mutați fereastra PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Extindeți fereastra PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Restrângeți fereastra PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Apăsați de două ori "<annotation icon="home_icon">"butonul ecran de pornire"</annotation>" pentru comenzi"</string> + <string name="pip_move" msgid="158770205886688553">"Mută"</string> + <string name="pip_expand" msgid="1051966011679297308">"Extinde"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Restrânge"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Apasă de 2 ori "<annotation icon="home_icon">"ECRANUL DE PORNIRE"</annotation>" pentru comenzi"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Meniu picture-in-picture."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mută la stânga"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mută la dreapta"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mută în sus"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mută în jos"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gata"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index 95bf1cf11435..489adc06e93d 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Настройки"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Включить разделение экрана"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню \"Картинка в картинке\""</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> находится в режиме \"Картинка в картинке\""</string> <string name="pip_notification_message" msgid="8854051911700302620">"Чтобы отключить эту функцию для приложения \"<xliff:g id="NAME">%s</xliff:g>\", перейдите в настройки."</string> <string name="pip_play" msgid="3496151081459417097">"Воспроизвести"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показать"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"В режиме разделения экрана приложение может работать нестабильно."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложение не поддерживает разделение экрана."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Это приложение можно открыть только в одном окне."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Приложение может не работать на дополнительном экране"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложение не поддерживает запуск на дополнительных экранах"</string> <string name="accessibility_divider" msgid="703810061635792791">"Разделитель экрана"</string> + <string name="divider_title" msgid="5482989479865361192">"Разделитель экрана"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Левый во весь экран"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Левый на 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левый на 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Всплывающая подсказка"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Настроить"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Всплывающий чат закрыт."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Нажмите, чтобы перезапустить приложение и перейти в полноэкранный режим."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Нажмите, чтобы перезапустить приложение и настроить удобный для просмотра вид"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Некоторые приложения лучше работают в вертикальном режиме"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Чтобы эффективно использовать экранное пространство, выполните одно из следующих действий:"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Чтобы перейти в полноэкранный режим, поверните устройство."</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Чтобы переместить приложение, нажмите на него дважды."</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Выполняйте несколько задач одновременно"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Чтобы переместить приложение, дважды нажмите рядом с ним."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОК"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Развернуть, чтобы узнать больше."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Свернуть"</string> + <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string> + <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> + <string name="handle_text" msgid="1766582106752184456">"Маркер"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Полноэкранный режим"</string> + <string name="desktop_text" msgid="1077633567027630454">"Режим компьютера"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Разделить экран"</string> + <string name="more_button_text" msgid="3655388105592893530">"Ещё"</string> + <string name="float_button_text" msgid="9221657008391364581">"Плавающее окно"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml index 08623e1e69c5..c8fb47913ec9 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Картинка в картинке"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Без названия)"</string> - <string name="pip_close" msgid="9135220303720555525">"\"Кадр в кадре\" – выйти"</string> + <string name="pip_close" msgid="2955969519031223530">"Закрыть"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Во весь экран"</string> - <string name="pip_move" msgid="1544227837964635439">"Переместить PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Развернуть PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Свернуть PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Элементы управления: дважды нажмите "<annotation icon="home_icon">" кнопку главного экрана "</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Переместить"</string> + <string name="pip_expand" msgid="1051966011679297308">"Развернуть"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Свернуть"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Параметры: дважды нажмите "<annotation icon="home_icon">"кнопку главного экрана"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Меню \"Картинка в картинке\"."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Переместить влево"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Переместить вправо"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Переместить вверх"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Переместить вниз"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Готово"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index 23dd65ad7b31..32371148fce2 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"සැකසීම්"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"බෙදුම් තිරයට ඇතුළු වන්න"</string> <string name="pip_menu_title" msgid="5393619322111827096">"මෙනුව"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"පින්තූරය තුළ පින්තූරය මෙනුව"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> පින්තූරය-තුළ-පින්තූරය තුළ වේ"</string> <string name="pip_notification_message" msgid="8854051911700302620">"ඔබට <xliff:g id="NAME">%s</xliff:g> මෙම විශේෂාංගය භාවිත කිරීමට අවශ්ය නැති නම්, සැකසීම් විවෘත කිරීමට තට්ටු කර එය ක්රියාවිරහිත කරන්න."</string> <string name="pip_play" msgid="3496151081459417097">"ධාවනය කරන්න"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"සඟවා තැබීම ඉවත් කරන්න"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"යෙදුම බෙදුම් තිරය සමග ක්රියා නොකළ හැකිය"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"යෙදුම බෙදුණු-තිරය සඳහා සහාය නොදක්වයි."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"යෙදුම ද්විතියික සංදර්ශකයක ක්රියා නොකළ හැකිය."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"යෙදුම ද්විතීයික සංදර්ශක මත දියත් කිරීම සඳහා සහාය නොදක්වයි."</string> <string name="accessibility_divider" msgid="703810061635792791">"බෙදුම්-තිර වෙන්කරණය"</string> + <string name="divider_title" msgid="5482989479865361192">"බෙදුම්-තිර වෙන්කරණය"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"වම් පූර්ණ තිරය"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"වම් 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"වම් 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"බුබුළු"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"කළමනා කරන්න"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"බුබුල ඉවත දමා ඇත."</string> - <string name="restart_button_description" msgid="5887656107651190519">"මෙම යෙදුම යළි ඇරඹීමට සහ පූර්ණ තිරයට යාමට තට්ටු කරන්න."</string> + <string name="restart_button_description" msgid="6712141648865547958">"වඩා හොඳ දසුනක් ලබා ගැනීම සඳහා මෙම යෙදුම යළි ඇරඹීමට තට්ටු කරන්න."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"සමහර යෙදුම් ප්රතිමූර්තිය තුළ හොඳින්ම ක්රියා කරයි"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ඔබගේ ඉඩෙන් උපරිම ප්රයෝජන ගැනීමට මෙම විකල්පවලින් එකක් උත්සාහ කරන්න"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"සම්පූර්ණ තිරයට යාමට ඔබගේ උපාංගය කරකවන්න"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"එය නැවත ස්ථානගත කිරීමට යෙදුමකට යාබදව දෙවරක් තට්ටු කරන්න"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"බලන්න සහ තවත් දේ කරන්න"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"යෙදුමක් නැවත ස්ථානගත කිරීමට පිටතින් දෙවරක් තට්ටු කරන්න"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"තේරුණා"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"වැඩිදුර තොරතුරු සඳහා දිග හරින්න"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string> + <string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string> + <string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string> + <string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string> + <string name="handle_text" msgid="1766582106752184456">"හැඬලය"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"පූර්ණ තිරය"</string> + <string name="desktop_text" msgid="1077633567027630454">"ඩෙස්ක්ටොප් ප්රකාරය"</string> + <string name="split_screen_text" msgid="1396336058129570886">"බෙදුම් තිරය"</string> + <string name="more_button_text" msgid="3655388105592893530">"තව"</string> + <string name="float_button_text" msgid="9221657008391364581">"පාවෙන"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-si/strings_tv.xml b/libs/WindowManager/Shell/res/values-si/strings_tv.xml index fbb0ebba0623..aa949ec75b3d 100644 --- a/libs/WindowManager/Shell/res/values-si/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-si/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"පින්තූරය-තුළ-පින්තූරය"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(මාතෘකාවක් නැති වැඩසටහන)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP වසන්න"</string> + <string name="pip_close" msgid="2955969519031223530">"වසන්න"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"සම්පූර්ණ තිරය"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP ගෙන යන්න"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP දිග හරින්න"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP හකුළන්න"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" පාලන සඳහා "<annotation icon="home_icon">" මුල් පිටුව "</annotation>" දෙවරක් ඔබන්න"</string> + <string name="pip_move" msgid="158770205886688553">"ගෙන යන්න"</string> + <string name="pip_expand" msgid="1051966011679297308">"දිග හරින්න"</string> + <string name="pip_collapse" msgid="3903295106641385962">"හකුළන්න"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"පාලන සඳහා "<annotation icon="home_icon">"නිවහන"</annotation>" දෙවරක් ඔබන්න"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"පින්තූරය තුළ පින්තූරය මෙනුව"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"වමට ගෙන යන්න"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"දකුණට ගෙන යන්න"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ඉහළට ගෙන යන්න"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"පහළට ගෙන යන්න"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"නිමයි"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index a231cacefb20..a7530215d010 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Nastavenia"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Prejsť na rozdelenú obrazovku"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Ponuka"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Ponuka obrazu v obraze"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je v režime obraz v obraze"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ak nechcete, aby aplikácia <xliff:g id="NAME">%s</xliff:g> používala túto funkciu, klepnutím otvorte nastavenia a vypnite ju."</string> <string name="pip_play" msgid="3496151081459417097">"Prehrať"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušiť skrytie"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikácia nemusí fungovať s rozdelenou obrazovkou."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikácia nepodporuje rozdelenú obrazovku."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Táto aplikácia môže byť otvorená iba v jednom okne."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikácia nemusí fungovať na sekundárnej obrazovke."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikácia nepodporuje spúšťanie na sekundárnych obrazovkách."</string> <string name="accessibility_divider" msgid="703810061635792791">"Rozdeľovač obrazovky"</string> + <string name="divider_title" msgid="5482989479865361192">"Rozdeľovač obrazovky"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ľavá – na celú obrazovku"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ľavá – 70 %"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ľavá – 50 %"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovať"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina bola zavretá."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Klepnutím reštartujete túto aplikáciu a prejdete do režimu celej obrazovky."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Ak chcete zlepšiť zobrazenie, klepnutím túto aplikáciu reštartujte."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s kamerou?\nKlepnutím znova upravte."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nevyriešilo sa to?\nKlepnutím sa vráťte."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemáte problémy s kamerou? Klepnutím zatvoríte."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Niektoré aplikácie fungujú najlepšie v režime na výšku"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Vyskúšajte jednu z týchto možností a využívajte svoj priestor naplno"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Otočením zariadenia prejdete do režimu celej obrazovky"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvojitým klepnutím vedľa aplikácie zmeníte jej pozíciu"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobrazte si a zvládnite toho viac"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Rozdelenú obrazovku aktivujete presunutím ďalšie aplikácie"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikácie zmeníte jej pozíciu"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Po rozbalení sa dozviete viac."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovať"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string> + <string name="back_button_text" msgid="1469718707134137085">"Späť"</string> + <string name="handle_text" msgid="1766582106752184456">"Rukoväť"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string> + <string name="desktop_text" msgid="1077633567027630454">"Režim počítača"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Rozdelená obrazovka"</string> + <string name="more_button_text" msgid="3655388105592893530">"Viac"</string> + <string name="float_button_text" msgid="9221657008391364581">"Plávajúce"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml index 81cb0eafc759..d5562d519d4b 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Obraz v obraze"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez názvu)"</string> - <string name="pip_close" msgid="9135220303720555525">"Zavrieť režim PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Zavrieť"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string> - <string name="pip_move" msgid="1544227837964635439">"Presunúť obraz v obraze"</string> - <string name="pip_expand" msgid="7605396312689038178">"Rozbaliť obraz v obraze"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Zbaliť obraz v obraze"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Ovládanie zobraz. dvoj. stlač. "<annotation icon="home_icon">" TLAČIDLA PLOCHY "</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Presunúť"</string> + <string name="pip_expand" msgid="1051966011679297308">"Rozbaliť"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Zbaliť"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Ovládanie zobrazíte dvojitým stlačením "<annotation icon="home_icon">"DOMOV"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Ponuka obrazu v obraze."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Posunúť doľava"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Posunúť doprava"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Posunúť nahor"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Posunúť nadol"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Hotovo"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index adeaae978eaa..b5d87333d85e 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Nastavitve"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Vklopi razdeljen zaslon"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meni za sliko v sliki"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je v načinu slika v sliki"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Če ne želite, da aplikacija <xliff:g id="NAME">%s</xliff:g> uporablja to funkcijo, se dotaknite, da odprete nastavitve, in funkcijo izklopite."</string> <string name="pip_play" msgid="3496151081459417097">"Predvajaj"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Razkrij"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podpira načina razdeljenega zaslona."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"To aplikacijo je mogoče odpreti samo v enem oknu."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija morda ne bo delovala na sekundarnem zaslonu."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podpira zagona na sekundarnih zaslonih."</string> <string name="accessibility_divider" msgid="703810061635792791">"Razdelilnik zaslonov"</string> + <string name="divider_title" msgid="5482989479865361192">"Razdelilnik zaslonov"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Levi v celozaslonski način"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Levi 70 %"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi 50 %"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Mehurček"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblaček je bil opuščen."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Dotaknite se za vnovični zagon te aplikacije in preklop v celozaslonski način."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Če želite boljši prikaz, se dotaknite za vnovični zagon te aplikacije."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Težave s fotoaparatom?\nDotaknite se za vnovično prilagoditev"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"To ni odpravilo težave?\nDotaknite se za povrnitev"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nimate težav s fotoaparatom? Dotaknite se za opustitev."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Nekatere aplikacije najbolje delujejo v navpični postavitvi"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Poskusite eno od teh možnosti za čim boljši izkoristek prostora"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Če želite preklopiti v celozaslonski način, zasukajte napravo."</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvakrat se dotaknite ob aplikaciji, če jo želite prestaviti."</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Oglejte si in naredite več"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Za razdeljeni zaslon povlecite sem še eno aplikacijo."</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvakrat se dotaknite zunaj aplikacije, če jo želite prestaviti."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimiraj"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zapri"</string> + <string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string> + <string name="handle_text" msgid="1766582106752184456">"Ročica"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Celozaslonsko"</string> + <string name="desktop_text" msgid="1077633567027630454">"Namizni način"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Razdeljen zaslon"</string> + <string name="more_button_text" msgid="3655388105592893530">"Več"</string> + <string name="float_button_text" msgid="9221657008391364581">"Lebdeče"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml index 060aaa0ce647..a37375e1ae9c 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Slika v sliki"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program brez naslova)"</string> - <string name="pip_close" msgid="9135220303720555525">"Zapri način PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Zapri"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Celozaslonsko"</string> - <string name="pip_move" msgid="1544227837964635439">"Premakni sliko v sliki"</string> - <string name="pip_expand" msgid="7605396312689038178">"Razširi sliko v sliki"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Strni sliko v sliki"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Za kontrolnike dvakrat pritisnite gumb za "<annotation icon="home_icon">" ZAČETNI ZASLON "</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Premakni"</string> + <string name="pip_expand" msgid="1051966011679297308">"Razširi"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Strni"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Za kontrolnike dvakrat pritisnite gumb za "<annotation icon="home_icon">"ZAČETNI ZASLON"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Meni za sliko v sliki"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Premakni levo"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Premakni desno"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Premakni navzgor"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Premakni navzdol"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Končano"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index 2839b4bae7e4..ebd644c7425f 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Cilësimet"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Hyr në ekranin e ndarë"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menyja"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menyja e \"Figurës brenda figurës\""</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> është në figurë brenda figurës"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Nëse nuk dëshiron që <xliff:g id="NAME">%s</xliff:g> ta përdorë këtë funksion, trokit për të hapur cilësimet dhe për ta çaktivizuar."</string> <string name="pip_play" msgid="3496151081459417097">"Luaj"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Mos e fshih"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacioni mund të mos funksionojë me ekranin e ndarë."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacioni nuk mbështet ekranin e ndarë."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ky aplikacion mund të hapet vetëm në 1 dritare."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacioni mund të mos funksionojë në një ekran dytësor."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacioni nuk mbështet nisjen në ekrane dytësore."</string> <string name="accessibility_divider" msgid="703810061635792791">"Ndarësi i ekranit të ndarë"</string> + <string name="divider_title" msgid="5482989479865361192">"Ndarësi i ekranit të ndarë"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ekrani i plotë majtas"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Majtas 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Majtas 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Flluskë"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Menaxho"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Flluska u hoq."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Trokit për ta rinisur këtë aplikacion dhe për të kaluar në ekranin e plotë."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Trokit për të rifilluar këtë aplikacion për një pamje më të mirë."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ka probleme me kamerën?\nTrokit për ta ripërshtatur"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nuk u rregullua?\nTrokit për ta rikthyer"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nuk ka probleme me kamerën? Trokit për ta shpërfillur."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Disa aplikacione funksionojnë më mirë në modalitetin vertikal"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Provo një nga këto opsione për ta shfrytëzuar sa më mirë hapësirën"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rrotullo ekranin për të kaluar në ekran të plotë"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Trokit dy herë pranë një aplikacioni për ta ripozicionuar"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Shiko dhe bëj më shumë"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Zvarrite në një aplikacion tjetër për ekranin e ndarë"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Trokit dy herë jashtë një aplikacioni për ta ripozicionuar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimizo"</string> + <string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string> + <string name="back_button_text" msgid="1469718707134137085">"Pas"</string> + <string name="handle_text" msgid="1766582106752184456">"Emërtimi"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Ekrani i plotë"</string> + <string name="desktop_text" msgid="1077633567027630454">"Modaliteti i desktopit"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Ekrani i ndarë"</string> + <string name="more_button_text" msgid="3655388105592893530">"Më shumë"</string> + <string name="float_button_text" msgid="9221657008391364581">"Pluskuese"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml index 9bfdb6a3edd8..3fbaaac2d3a2 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Figurë brenda figurës"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program pa titull)"</string> - <string name="pip_close" msgid="9135220303720555525">"Mbyll PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Mbyll"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ekrani i plotë"</string> - <string name="pip_move" msgid="1544227837964635439">"Zhvendos PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Zgjero PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Palos PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Trokit dy herë "<annotation icon="home_icon">" KREU "</annotation>" për kontrollet"</string> + <string name="pip_move" msgid="158770205886688553">"Lëviz"</string> + <string name="pip_expand" msgid="1051966011679297308">"Zgjero"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Palos"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Trokit dy herë te "<annotation icon="home_icon">"KREU"</annotation>" për kontrollet"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menyja e \"Figurës brenda figurës\"."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Lëviz majtas"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Lëviz djathtas"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Lëviz lart"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Lëviz poshtë"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"U krye"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index 9db6b7c63610..d051ca350682 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Подешавања"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Уђи на подељени екран"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Мени"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Мени слике у слици."</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> је слика у слици"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ако не желите да <xliff:g id="NAME">%s</xliff:g> користи ову функцију, додирните да бисте отворили подешавања и искључили је."</string> <string name="pip_play" msgid="3496151081459417097">"Пусти"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Уклоните из тајне меморије"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Апликација можда неће радити са подељеним екраном."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликација не подржава подељени екран."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ова апликација може да се отвори само у једном прозору."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликација можда неће функционисати на секундарном екрану."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликација не подржава покретање на секундарним екранима."</string> <string name="accessibility_divider" msgid="703810061635792791">"Разделник подељеног екрана"</string> + <string name="divider_title" msgid="5482989479865361192">"Разделник подељеног екрана"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Режим целог екрана за леви екран"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Леви екран 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Леви екран 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Облачић"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Управљајте"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Облачић је одбачен."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Додирните да бисте рестартовали апликацију и прешли у режим целог екрана."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Додирните да бисте рестартовали ову апликацију ради бољег приказа."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Неке апликације најбоље функционишу у усправном режиму"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Испробајте једну од ових опција да бисте на најбољи начин искористили простор"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ротирајте уређај за приказ преко целог екрана"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Двапут додирните поред апликације да бисте променили њену позицију"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Видите и урадите више"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Превуците другу апликацију да бисте користили подељени екран"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двапут додирните изван апликације да бисте променили њену позицију"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширите за још информација."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Умањите"</string> + <string name="close_button_text" msgid="2913281996024033299">"Затворите"</string> + <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> + <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Преко целог екрана"</string> + <string name="desktop_text" msgid="1077633567027630454">"Режим за рачунаре"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Подељени екран"</string> + <string name="more_button_text" msgid="3655388105592893530">"Још"</string> + <string name="float_button_text" msgid="9221657008391364581">"Плутајуће"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml index 6bc5c87bab48..34950027772b 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Слика у слици"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програм без наслова)"</string> - <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Затвори"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Цео екран"</string> - <string name="pip_move" msgid="1544227837964635439">"Премести слику у слици"</string> - <string name="pip_expand" msgid="7605396312689038178">"Прошири слику у слици"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Скупи слику у слици"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Двапут притисните "<annotation icon="home_icon">" HOME "</annotation>" за контроле"</string> + <string name="pip_move" msgid="158770205886688553">"Премести"</string> + <string name="pip_expand" msgid="1051966011679297308">"Прошири"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Скупи"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Двапут притисните "<annotation icon="home_icon">" HOME "</annotation>" за контроле"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Мени Слика у слици."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Померите налево"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Померите надесно"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Померите нагоре"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Померите надоле"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Готово"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index f6bd55423cdc..cd46039b26b5 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Inställningar"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Starta delad skärm"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Meny"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Bild-i-bild-meny"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> visas i bild-i-bild"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Om du inte vill att den här funktionen används i <xliff:g id="NAME">%s</xliff:g> öppnar du inställningarna genom att trycka. Sedan inaktiverar du funktionen."</string> <string name="pip_play" msgid="3496151081459417097">"Spela upp"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Återställ stash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Appen kanske inte fungerar med delad skärm."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen har inte stöd för delad skärm."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denna app kan bara vara öppen i ett fönster."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen kanske inte fungerar på en sekundär skärm."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan inte köras på en sekundär skärm."</string> <string name="accessibility_divider" msgid="703810061635792791">"Avdelare för delad skärm"</string> + <string name="divider_title" msgid="5482989479865361192">"Avdelare för delad skärm"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Helskärm på vänster skärm"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vänster 70 %"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vänster 50 %"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubbla"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Hantera"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubblan ignorerades."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Tryck för att starta om appen i helskärmsläge."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Tryck för att starta om appen och få en bättre vy."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problem med kameran?\nTryck för att anpassa på nytt"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Löstes inte problemet?\nTryck för att återställa"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Inga problem med kameran? Tryck för att ignorera."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Vissa appar fungerar bäst i stående läge"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Testa med ett av dessa alternativ för att få ut mest möjliga av ytan"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotera skärmen för att gå över till helskärmsläge"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tryck snabbt två gånger bredvid en app för att flytta den"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se och gör mer"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dra till en annan app för läget Delad skärm"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryck snabbt två gånger utanför en app för att flytta den"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Minimera"</string> + <string name="close_button_text" msgid="2913281996024033299">"Stäng"</string> + <string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string> + <string name="handle_text" msgid="1766582106752184456">"Handtag"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Helskärm"</string> + <string name="desktop_text" msgid="1077633567027630454">"Datorläge"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Delad skärm"</string> + <string name="more_button_text" msgid="3655388105592893530">"Mer"</string> + <string name="float_button_text" msgid="9221657008391364581">"Svävande"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml index b3465ab1db85..7116ac162fbd 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Bild-i-bild"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Namnlöst program)"</string> - <string name="pip_close" msgid="9135220303720555525">"Stäng PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Stäng"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Helskärm"</string> - <string name="pip_move" msgid="1544227837964635439">"Flytta BIB"</string> - <string name="pip_expand" msgid="7605396312689038178">"Utöka bild-i-bild"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Komprimera bild-i-bild"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Tryck snabbt två gånger på "<annotation icon="home_icon">" HEM "</annotation>" för kontroller"</string> + <string name="pip_move" msgid="158770205886688553">"Flytta"</string> + <string name="pip_expand" msgid="1051966011679297308">"Utöka"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Komprimera"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Tryck snabbt två gånger på "<annotation icon="home_icon">"HEM"</annotation>" för inställningar"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Bild-i-bild-meny."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Flytta åt vänster"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Flytta åt höger"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Flytta uppåt"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Flytta nedåt"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Klar"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index f6e558527ee5..345fbf81cd48 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Mipangilio"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Weka skrini iliyogawanywa"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menyu ya kipengele cha Kupachika Picha ndani ya Picha nyingine."</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> iko katika hali ya picha ndani ya picha nyingine"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Ikiwa hutaki <xliff:g id="NAME">%s</xliff:g> itumie kipengele hiki, gusa ili ufungue mipangilio na uizime."</string> <string name="pip_play" msgid="3496151081459417097">"Cheza"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Huenda programu isifanye kazi kwenye skrini inayogawanywa."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programu haiwezi kutumia skrini iliyogawanywa."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string> <string name="accessibility_divider" msgid="703810061635792791">"Kitenganishi cha skrini inayogawanywa"</string> + <string name="divider_title" msgid="5482989479865361192">"Kitenganishi cha kugawa skrini"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Skrini nzima ya kushoto"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kushoto 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kushoto 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Kiputo"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Dhibiti"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Umeondoa kiputo."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Gusa ili uzime na uwashe programu hii, kisha nenda kwenye skrini nzima."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Gusa ili uzime kisha uwashe programu hii, ili upate mwonekano bora."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Je, kuna hitilafu za kamera?\nGusa ili urekebishe"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Umeshindwa kurekebisha?\nGusa ili urejeshe nakala ya awali"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Je, hakuna hitilafu za kamera? Gusa ili uondoe."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Baadhi ya programu hufanya kazi vizuri zaidi zikiwa wima"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Jaribu moja kati ya chaguo hizi ili utumie nafasi ya skrini yako kwa ufanisi"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zungusha kifaa chako ili uende kwenye hali ya skrini nzima"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Gusa mara mbili karibu na programu ili uihamishe"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Angalia na ufanye zaidi"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Buruta ndani programu nyingine ili utumie hali ya skrini iliyogawanywa"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Gusa mara mbili nje ya programu ili uihamishe"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Punguza"</string> + <string name="close_button_text" msgid="2913281996024033299">"Funga"</string> + <string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string> + <string name="handle_text" msgid="1766582106752184456">"Ncha"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Skrini nzima"</string> + <string name="desktop_text" msgid="1077633567027630454">"Hali ya Kompyuta ya mezani"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Gawa Skrini"</string> + <string name="more_button_text" msgid="3655388105592893530">"Zaidi"</string> + <string name="float_button_text" msgid="9221657008391364581">"Inayoelea"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml index baff49ed821a..1e9406f01433 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pachika Picha Ndani ya Picha Nyingine"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programu isiyo na jina)"</string> - <string name="pip_close" msgid="9135220303720555525">"Funga PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Funga"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Skrini nzima"</string> - <string name="pip_move" msgid="1544227837964635439">"Kuhamisha PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Panua PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Kunja PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Bonyeza mara mbili kitufe cha "<annotation icon="home_icon">" UKURASA WA KWANZA "</annotation>" kupata vidhibiti"</string> + <string name="pip_move" msgid="158770205886688553">"Hamisha"</string> + <string name="pip_expand" msgid="1051966011679297308">"Panua"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Kunja"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Bonyeza mara mbili kitufe cha "<annotation icon="home_icon">" UKURASA WA KWANZA "</annotation>" kupata vidhibiti"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menyu ya kipengele cha kupachika picha ndani ya picha nyingine."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Sogeza kushoto"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Sogeza kulia"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Sogeza juu"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Sogeza chini"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Imemaliza"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index d8334adfe5ef..525f2ea043fc 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"அமைப்புகள்"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"திரைப் பிரிப்பு பயன்முறைக்குச் செல்"</string> <string name="pip_menu_title" msgid="5393619322111827096">"மெனு"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"பிக்ச்சர்-இன்-பிக்ச்சர் மெனு"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> தற்போது பிக்ச்சர்-இன்-பிக்ச்சரில் உள்ளது"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> இந்த அம்சத்தைப் பயன்படுத்த வேண்டாம் என நினைத்தால் இங்கு தட்டி அமைப்புகளைத் திறந்து இதை முடக்கவும்."</string> <string name="pip_play" msgid="3496151081459417097">"இயக்கு"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"திரைப் பிரிப்பு அம்சத்தில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"திரையைப் பிரிப்பதைப் ஆப்ஸ் ஆதரிக்கவில்லை."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"இந்த ஆப்ஸை 1 சாளரத்தில் மட்டுமே திறக்க முடியும்."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"இரண்டாம்நிலைத் திரையில் ஆப்ஸ் வேலை செய்யாமல் போகக்கூடும்."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"இரண்டாம்நிலைத் திரைகளில் பயன்பாட்டைத் தொடங்க முடியாது."</string> <string name="accessibility_divider" msgid="703810061635792791">"திரையைப் பிரிக்கும் பிரிப்பான்"</string> + <string name="divider_title" msgid="5482989479865361192">"திரைப் பிரிப்பான்"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"இடது புறம் முழுத் திரை"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"இடது புறம் 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"இடது புறம் 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"பபிள்"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"நிர்வகி"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"குமிழ் நிராகரிக்கப்பட்டது."</string> - <string name="restart_button_description" msgid="5887656107651190519">"தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கலாம், முழுத்திரையில் பார்க்கலாம்."</string> + <string name="restart_button_description" msgid="6712141648865547958">"இங்கு தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கி, ஆப்ஸ் காட்டப்படும் விதத்தை இன்னும் சிறப்பாக்கலாம்."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"சில ஆப்ஸ் \'போர்ட்ரெய்ட்டில்\' சிறப்பாகச் செயல்படும்"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ஸ்பேஸ்களிலிருந்து அதிகப் பலன்களைப் பெற இந்த விருப்பங்களில் ஒன்றைப் பயன்படுத்துங்கள்"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"முழுத்திரைக்குச் செல்ல உங்கள் சாதனத்தைச் சுழற்றவும்"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ஆப்ஸை இடம் மாற்ற, ஆப்ஸுக்கு அடுத்து இருமுறை தட்டவும்"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"பலவற்றைப் பார்த்தல் மற்றும் செய்தல்"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ஆப்ஸை இடம் மாற்ற அதன் வெளியில் இருமுறை தட்டலாம்"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"சரி"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string> + <string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string> + <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string> + <string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string> + <string name="handle_text" msgid="1766582106752184456">"ஹேண்டில்"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"முழுத்திரை"</string> + <string name="desktop_text" msgid="1077633567027630454">"டெஸ்க்டாப் பயன்முறை"</string> + <string name="split_screen_text" msgid="1396336058129570886">"திரையைப் பிரிக்கும்"</string> + <string name="more_button_text" msgid="3655388105592893530">"கூடுதல் விருப்பத்தேர்வுகள்"</string> + <string name="float_button_text" msgid="9221657008391364581">"மிதக்கும் சாளரம்"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml index 4439e299c919..ef1bcf913eaf 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"பிக்ச்சர்-இன்-பிக்ச்சர்"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(தலைப்பு இல்லை)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIPஐ மூடு"</string> + <string name="pip_close" msgid="2955969519031223530">"மூடுக"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"முழுத்திரை"</string> - <string name="pip_move" msgid="1544227837964635439">"PIPபை நகர்த்து"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIPபை விரிவாக்கு"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIPபைச் சுருக்கு"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" கட்டுப்பாடுகள்: "<annotation icon="home_icon">" முகப்பு "</annotation>" பட்டனை இருமுறை அழுத்துக"</string> + <string name="pip_move" msgid="158770205886688553">"நகர்த்து"</string> + <string name="pip_expand" msgid="1051966011679297308">"விரி"</string> + <string name="pip_collapse" msgid="3903295106641385962">"சுருக்கு"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"கட்டுப்பாடுகளுக்கு "<annotation icon="home_icon">"முகப்பு"</annotation>" பட்டனை இருமுறை அழுத்துக"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"பிக்ச்சர்-இன்-பிக்ச்சர் மெனு."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"இடப்புறம் நகர்த்து"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"வலப்புறம் நகர்த்து"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"மேலே நகர்த்து"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"கீழே நகர்த்து"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"முடிந்தது"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index 733075550007..0c0114afeed2 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -22,20 +22,23 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"సెట్టింగ్లు"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"స్ప్లిట్ స్క్రీన్ను ఎంటర్ చేయండి"</string> <string name="pip_menu_title" msgid="5393619322111827096">"మెనూ"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"పిక్చర్-ఇన్-పిక్చర్ మెనూ"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> చిత్రంలో చిత్రం రూపంలో ఉంది"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ఈ లక్షణాన్ని ఉపయోగించకూడదు అని మీరు అనుకుంటే, సెట్టింగ్లను తెరవడానికి ట్యాప్ చేసి, దీన్ని ఆఫ్ చేయండి."</string> <string name="pip_play" msgid="3496151081459417097">"ప్లే చేయి"</string> <string name="pip_pause" msgid="690688849510295232">"పాజ్ చేయి"</string> <string name="pip_skip_to_next" msgid="8403429188794867653">"దాటవేసి తర్వాత దానికి వెళ్లు"</string> <string name="pip_skip_to_prev" msgid="7172158111196394092">"దాటవేసి మునుపటి దానికి వెళ్లు"</string> - <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"పరిమాణం మార్చు"</string> + <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"సైజ్ మార్చు"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"స్టాచ్"</string> <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్స్టాచ్"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్ పని చేయకపోవచ్చు."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్లో స్క్రీన్ విభజనకు మద్దతు లేదు."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ఈ యాప్ను 1 విండోలో మాత్రమే తెరవవచ్చు."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string> <string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string> + <string name="divider_title" msgid="5482989479865361192">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు ఫుల్-స్క్రీన్"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ఎడమవైపు 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ఎడమవైపు 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"బబుల్"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"మేనేజ్ చేయండి"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"బబుల్ విస్మరించబడింది."</string> - <string name="restart_button_description" msgid="5887656107651190519">"ఈ యాప్ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేసి, ఆపై పూర్తి స్క్రీన్లోకి వెళ్లండి."</string> + <string name="restart_button_description" msgid="6712141648865547958">"మెరుగైన వీక్షణ కోసం ఈ యాప్ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేయండి."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"కొన్ని యాప్లు పోర్ట్రెయిట్లో ఉత్తమంగా పని చేస్తాయి"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"మీ ప్రదేశాన్ని ఎక్కువగా ఉపయోగించుకోవడానికి ఈ ఆప్షన్లలో ఒకదాన్ని ట్రై చేయండి"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ఫుల్ స్క్రీన్కు వెళ్లడానికి మీ పరికరాన్ని తిప్పండి"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"యాప్ స్థానాన్ని మార్చడానికి దాని పక్కన డబుల్-ట్యాప్ చేయండి"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"చూసి, మరిన్ని చేయండి"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"స్ప్లిట్-స్క్రీన్ కోసం మరొక యాప్లోకి లాగండి"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"యాప్ స్థానాన్ని మార్చడానికి దాని వెలుపల డబుల్-ట్యాప్ చేయండి"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"అర్థమైంది"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"మరింత సమాచారం కోసం విస్తరించండి."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string> + <string name="minimize_button_text" msgid="271592547935841753">"కుదించండి"</string> + <string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string> + <string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string> + <string name="handle_text" msgid="1766582106752184456">"హ్యాండిల్"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"ఫుల్-స్క్రీన్"</string> + <string name="desktop_text" msgid="1077633567027630454">"డెస్క్టాప్ మోడ్"</string> + <string name="split_screen_text" msgid="1396336058129570886">"స్ప్లిట్ స్క్రీన్"</string> + <string name="more_button_text" msgid="3655388105592893530">"మరిన్ని"</string> + <string name="float_button_text" msgid="9221657008391364581">"ఫ్లోట్"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-te/strings_tv.xml b/libs/WindowManager/Shell/res/values-te/strings_tv.xml index 35579346615f..d9237dff6dd8 100644 --- a/libs/WindowManager/Shell/res/values-te/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-te/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"పిక్చర్-ఇన్-పిక్చర్"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(శీర్షిక లేని ప్రోగ్రామ్)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIPని మూసివేయి"</string> + <string name="pip_close" msgid="2955969519031223530">"మూసివేయండి"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ఫుల్-స్క్రీన్"</string> - <string name="pip_move" msgid="1544227837964635439">"PIPను తరలించండి"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIPని విస్తరించండి"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIPని కుదించండి"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" కంట్రోల్స్ కోసం "<annotation icon="home_icon">" HOME "</annotation>" బటన్ రెండుసార్లు నొక్కండి"</string> + <string name="pip_move" msgid="158770205886688553">"తరలించండి"</string> + <string name="pip_expand" msgid="1051966011679297308">"విస్తరించండి"</string> + <string name="pip_collapse" msgid="3903295106641385962">"కుదించండి"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"కంట్రోల్స్ కోసం "<annotation icon="home_icon">"HOME"</annotation>" బటన్ రెండుసార్లు నొక్కండి"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"పిక్చర్-ఇన్-పిక్చర్ మెనూ."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ఎడమ వైపుగా జరపండి"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"కుడి వైపుగా జరపండి"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"పైకి జరపండి"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"కిందికి జరపండి"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"పూర్తయింది"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-television/dimen.xml b/libs/WindowManager/Shell/res/values-television/dimen.xml index 14e89f8b08df..376cc4faca6f 100644 --- a/libs/WindowManager/Shell/res/values-television/dimen.xml +++ b/libs/WindowManager/Shell/res/values-television/dimen.xml @@ -21,4 +21,7 @@ <!-- Padding between PIP and keep clear areas that caused it to move. --> <dimen name="pip_keep_clear_area_padding">16dp</dimen> + + <!-- The corner radius for PiP window. --> + <dimen name="pip_corner_radius">0dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index cfee8ea3242e..9f3a14610225 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"การตั้งค่า"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"เข้าสู่โหมดแบ่งหน้าจอ"</string> <string name="pip_menu_title" msgid="5393619322111827096">"เมนู"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"เมนูการแสดงภาพซ้อนภาพ"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ใช้การแสดงภาพซ้อนภาพ"</string> <string name="pip_notification_message" msgid="8854051911700302620">"หากคุณไม่ต้องการให้ <xliff:g id="NAME">%s</xliff:g> ใช้ฟีเจอร์นี้ ให้แตะเพื่อเปิดการตั้งค่าแล้วปิดฟีเจอร์"</string> <string name="pip_play" msgid="3496151081459417097">"เล่น"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"เอาออกจากที่เก็บส่วนตัว"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"แอปอาจใช้ไม่ได้กับโหมดแบ่งหน้าจอ"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"แอปไม่สนับสนุนการแยกหน้าจอ"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"แอปอาจไม่ทำงานในจอแสดงผลรอง"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"แอปไม่รองรับการเรียกใช้ในจอแสดงผลรอง"</string> <string name="accessibility_divider" msgid="703810061635792791">"เส้นแบ่งหน้าจอ"</string> + <string name="divider_title" msgid="5482989479865361192">"เส้นแยกหน้าจอ"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"เต็มหน้าจอทางซ้าย"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ซ้าย 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ซ้าย 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"บับเบิล"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"จัดการ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ปิดบับเบิลแล้ว"</string> - <string name="restart_button_description" msgid="5887656107651190519">"แตะเพื่อรีสตาร์ทแอปนี้และแสดงแบบเต็มหน้าจอ"</string> + <string name="restart_button_description" msgid="6712141648865547958">"แตะเพื่อรีสตาร์ทแอปนี้และรับมุมมองที่ดียิ่งขึ้น"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"บางแอปทำงานได้ดีที่สุดในแนวตั้ง"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"ลองใช้หนึ่งในตัวเลือกเหล่านี้เพื่อให้ได้ประโยชน์สูงสุดจากพื้นที่ว่าง"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"หมุนอุปกรณ์ให้แสดงเต็มหน้าจอ"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"แตะสองครั้งข้างแอปเพื่อเปลี่ยนตำแหน่ง"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"รับชมและทำสิ่งต่างๆ ได้มากขึ้น"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"แตะสองครั้งด้านนอกแอปเพื่อเปลี่ยนตำแหน่ง"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"รับทราบ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ขยายเพื่อดูข้อมูลเพิ่มเติม"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string> + <string name="minimize_button_text" msgid="271592547935841753">"ย่อ"</string> + <string name="close_button_text" msgid="2913281996024033299">"ปิด"</string> + <string name="back_button_text" msgid="1469718707134137085">"กลับ"</string> + <string name="handle_text" msgid="1766582106752184456">"แฮนเดิล"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"เต็มหน้าจอ"</string> + <string name="desktop_text" msgid="1077633567027630454">"โหมดเดสก์ท็อป"</string> + <string name="split_screen_text" msgid="1396336058129570886">"แยกหน้าจอ"</string> + <string name="more_button_text" msgid="3655388105592893530">"เพิ่มเติม"</string> + <string name="float_button_text" msgid="9221657008391364581">"ล่องลอย"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-th/strings_tv.xml b/libs/WindowManager/Shell/res/values-th/strings_tv.xml index 0a07d157ec6f..47a6bd1be812 100644 --- a/libs/WindowManager/Shell/res/values-th/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-th/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"การแสดงภาพซ้อนภาพ"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ไม่มีชื่อรายการ)"</string> - <string name="pip_close" msgid="9135220303720555525">"ปิด PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"ปิด"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"เต็มหน้าจอ"</string> - <string name="pip_move" msgid="1544227837964635439">"ย้าย PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"ขยาย PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"ยุบ PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" กดปุ่ม "<annotation icon="home_icon">" หน้าแรก "</annotation>" สองครั้งเพื่อเปิดการควบคุม"</string> + <string name="pip_move" msgid="158770205886688553">"ย้าย"</string> + <string name="pip_expand" msgid="1051966011679297308">"ขยาย"</string> + <string name="pip_collapse" msgid="3903295106641385962">"ยุบ"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"กดปุ่ม "<annotation icon="home_icon">" หน้าแรก "</annotation>" สองครั้งเพื่อเปิดการควบคุม"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"เมนูการแสดงภาพซ้อนภาพ"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ย้ายไปทางซ้าย"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ย้ายไปทางขวา"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ย้ายขึ้น"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ย้ายลง"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"เสร็จ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index eed624dd5069..c20a07f1cf90 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Mga Setting"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Pumasok sa split screen"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu ng Picture-in-Picture"</string> <string name="pip_notification_title" msgid="1347104727641353453">"Nasa picture-in-picture ang <xliff:g id="NAME">%s</xliff:g>"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Kung ayaw mong magamit ni <xliff:g id="NAME">%s</xliff:g> ang feature na ito, i-tap upang buksan ang mga setting at i-off ito."</string> <string name="pip_play" msgid="3496151081459417097">"I-play"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"I-unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Posibleng hindi gumana ang app sa split screen."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Hindi sinusuportahan ng app ang split-screen."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Sa 1 window lang puwedeng buksan ang app na ito."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Maaaring hindi gumana ang app sa pangalawang display."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Hindi sinusuportahan ng app ang paglulunsad sa mga pangalawang display."</string> <string name="accessibility_divider" msgid="703810061635792791">"Divider ng split-screen"</string> + <string name="divider_title" msgid="5482989479865361192">"Divider ng split-screen"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"I-full screen ang nasa kaliwa"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Gawing 70% ang nasa kaliwa"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Gawing 50% ang nasa kaliwa"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Pamahalaan"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Na-dismiss na ang bubble."</string> - <string name="restart_button_description" msgid="5887656107651190519">"I-tap para i-restart ang app na ito at mag-full screen."</string> + <string name="restart_button_description" msgid="6712141648865547958">"I-tap para i-restart ang app na ito para sa mas magandang view."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"May mga isyu sa camera?\nI-tap para i-refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Hindi ito naayos?\nI-tap para i-revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Walang isyu sa camera? I-tap para i-dismiss."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"May ilang app na pinakamainam gamitin nang naka-portrait"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Subukan ang isa sa mga opsyong ito para masulit ang iyong space"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"I-rotate ang iyong device para mag-full screen"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Mag-double tap sa tabi ng isang app para iposisyon ito ulit"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Tumingin at gumawa ng higit pa"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Mag-drag ng ibang app para sa split screen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Mag-double tap sa labas ng app para baguhin ang posisyon nito"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"I-expand para sa higit pang impormasyon."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"I-maximize"</string> + <string name="minimize_button_text" msgid="271592547935841753">"I-minimize"</string> + <string name="close_button_text" msgid="2913281996024033299">"Isara"</string> + <string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string> + <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string> + <string name="more_button_text" msgid="3655388105592893530">"Higit pa"</string> + <string name="float_button_text" msgid="9221657008391364581">"Float"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml index 9a11a38fa492..2d890d4126b6 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Walang pamagat na programa)"</string> - <string name="pip_close" msgid="9135220303720555525">"Isara ang PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Isara"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> - <string name="pip_move" msgid="1544227837964635439">"Ilipat ang PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"I-expand ang PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"I-collapse ang PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" I-double press ang "<annotation icon="home_icon">" HOME "</annotation>" para sa mga kontrol"</string> + <string name="pip_move" msgid="158770205886688553">"Ilipat"</string> + <string name="pip_expand" msgid="1051966011679297308">"I-expand"</string> + <string name="pip_collapse" msgid="3903295106641385962">"I-collapse"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"I-double press ang "<annotation icon="home_icon">"HOME"</annotation>" para sa mga kontrol"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu ng Picture-in-Picture."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Ilipat pakaliwa"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Ilipat pakanan"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Itaas"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Ibaba"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Tapos na"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index 2b4a2d0550f0..aeb86da2a6ed 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Ayarlar"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Bölünmüş ekrana geç"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Pencere içinde pencere menüsü"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>, pencere içinde pencere özelliğini kullanıyor"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> uygulamasının bu özelliği kullanmasını istemiyorsanız dokunarak ayarları açın ve söz konusu özelliği kapatın."</string> <string name="pip_play" msgid="3496151081459417097">"Oynat"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Depolama"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Uygulama bölünmüş ekranda çalışmayabilir."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uygulama bölünmüş ekranı desteklemiyor."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu uygulama yalnızca 1 pencerede açılabilir."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uygulama ikincil ekranda çalışmayabilir."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uygulama ikincil ekranlarda başlatılmayı desteklemiyor."</string> <string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcı"</string> + <string name="divider_title" msgid="5482989479865361192">"Bölünmüş ekran ayırıcı"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Solda tam ekran"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Solda %70"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Solda %50"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Baloncuk"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Yönet"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon kapatıldı."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Bu uygulamayı yeniden başlatmak ve tam ekrana geçmek için dokunun."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Bu uygulamayı yeniden başlatarak daha iyi bir görünüm elde etmek için dokunun."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kameranızda sorun mu var?\nDüzeltmek için dokunun"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bu işlem sorunu düzeltmedi mi?\nİşlemi geri almak için dokunun"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kameranızda sorun yok mu? Kapatmak için dokunun."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Bazı uygulamalar dikey modda en iyi performansı gösterir"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Alanınızı en verimli şekilde kullanmak için bu seçeneklerden birini deneyin"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Tam ekrana geçmek için cihazınızı döndürün"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Yeniden konumlandırmak için uygulamanın yanına iki kez dokunun"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daha fazlasını görün ve yapın"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Bölünmüş ekran için başka bir uygulamayı sürükleyin"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Yeniden konumlandırmak için uygulamanın dışına iki kez dokunun"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Küçült"</string> + <string name="close_button_text" msgid="2913281996024033299">"Kapat"</string> + <string name="back_button_text" msgid="1469718707134137085">"Geri"</string> + <string name="handle_text" msgid="1766582106752184456">"Herkese açık kullanıcı adı"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string> + <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Modu"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string> + <string name="more_button_text" msgid="3655388105592893530">"Daha Fazla"</string> + <string name="float_button_text" msgid="9221657008391364581">"Havada Süzülen"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml index bf4bc6f1fff7..9e3e59b74c19 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pencere İçinde Pencere"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıksız program)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP\'yi kapat"</string> + <string name="pip_close" msgid="2955969519031223530">"Kapat"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP\'yi taşı"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP penceresini genişlet"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP penceresini daralt"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Kontroller için "<annotation icon="home_icon">" ANA SAYFA "</annotation>"\'ya iki kez basın"</string> + <string name="pip_move" msgid="158770205886688553">"Taşı"</string> + <string name="pip_expand" msgid="1051966011679297308">"Genişlet"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Daralt"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Kontroller için "<annotation icon="home_icon">"ANA EKRAN"</annotation>" düğmesine iki kez basın"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Pencere içinde pencere menüsü."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Sola taşı"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Sağa taşı"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Yukarı taşı"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Aşağı taşı"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Bitti"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml index 02e726fbc3bf..0b61d7a85d9e 100644 --- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml +++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml @@ -15,20 +15,20 @@ limitations under the License. --> <resources> - <!-- The dimensions to user for picture-in-picture action buttons. --> - <dimen name="pip_menu_button_size">48dp</dimen> - <dimen name="pip_menu_button_radius">20dp</dimen> - <dimen name="pip_menu_icon_size">20dp</dimen> - <dimen name="pip_menu_button_margin">4dp</dimen> - <dimen name="pip_menu_button_wrapper_margin">26dp</dimen> - <dimen name="pip_menu_border_width">4dp</dimen> - <integer name="pip_menu_fade_animation_duration">500</integer> + <!-- The dimensions to use for tv window menu action buttons. --> + <dimen name="tv_window_menu_button_size">48dp</dimen> + <dimen name="tv_window_menu_button_radius">20dp</dimen> + <dimen name="tv_window_menu_icon_size">20dp</dimen> + <dimen name="tv_window_menu_button_margin">4dp</dimen> + <integer name="tv_window_menu_fade_animation_duration">500</integer> <!-- The pip menu front border corner radius is 2dp smaller than the background corner radius to hide the background from showing through. --> <dimen name="pip_menu_border_corner_radius">4dp</dimen> <dimen name="pip_menu_background_corner_radius">6dp</dimen> + <dimen name="pip_menu_border_width">4dp</dimen> <dimen name="pip_menu_outer_space">24dp</dimen> + <dimen name="pip_menu_button_start_end_offset">30dp</dimen> <!-- outer space minus border width --> <dimen name="pip_menu_outer_space_frame">20dp</dimen> @@ -41,8 +41,10 @@ <dimen name="pip_menu_edu_text_view_height">24dp</dimen> <dimen name="pip_menu_edu_text_home_icon">9sp</dimen> <dimen name="pip_menu_edu_text_home_icon_outline">14sp</dimen> - <integer name="pip_edu_text_show_duration_ms">10500</integer> - <integer name="pip_edu_text_window_exit_animation_duration_ms">1000</integer> - <integer name="pip_edu_text_view_exit_animation_duration_ms">300</integer> + <integer name="pip_edu_text_scroll_times">2</integer> + <integer name="pip_edu_text_non_scroll_show_duration">10500</integer> + <integer name="pip_edu_text_start_scroll_delay">2000</integer> + <integer name="pip_edu_text_window_exit_animation_duration">1000</integer> + <integer name="pip_edu_text_view_exit_animation_duration">300</integer> </resources> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index c3411a837c78..b589ed8c7739 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Налаштування"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Розділити екран"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню \"Картинка в картинці\""</string> <string name="pip_notification_title" msgid="1347104727641353453">"У додатку <xliff:g id="NAME">%s</xliff:g> є функція \"Картинка в картинці\""</string> <string name="pip_notification_message" msgid="8854051911700302620">"Щоб додаток <xliff:g id="NAME">%s</xliff:g> не використовував цю функцію, вимкніть її в налаштуваннях."</string> <string name="pip_play" msgid="3496151081459417097">"Відтворити"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Додаток може не працювати в режимі розділеного екрана."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Додаток не підтримує розділення екрана."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Цей додаток можна відкрити лише в одному вікні."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string> <string name="accessibility_divider" msgid="703810061635792791">"Розділювач екрана"</string> + <string name="divider_title" msgid="5482989479865361192">"Розділювач екрана"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ліве вікно на весь екран"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ліве вікно на 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ліве вікно на 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Спливаюче сповіщення"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Налаштувати"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Спливаюче сповіщення закрито."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Натисніть, щоб перезапустити додаток і перейти в повноекранний режим."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Натисніть, щоб перезапустити цей додаток для зручнішого перегляду."</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Деякі додатки найкраще працюють у вертикальній орієнтації"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Щоб максимально ефективно використовувати місце на екрані, спробуйте виконати одну з наведених нижче дій"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Щоб перейти в повноекранний режим, поверніть пристрій"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Щоб перемістити додаток, двічі торкніться області поруч із ним"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Більше простору та можливостей"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Щоб перемістити додаток, двічі торкніться області поза ним"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string> + <string name="close_button_text" msgid="2913281996024033299">"Закрити"</string> + <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> + <string name="handle_text" msgid="1766582106752184456">"Маркер"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"На весь екран"</string> + <string name="desktop_text" msgid="1077633567027630454">"Режим комп’ютера"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Розділити екран"</string> + <string name="more_button_text" msgid="3655388105592893530">"Більше"</string> + <string name="float_button_text" msgid="9221657008391364581">"Плаваюче вікно"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml index 7e9f54e68f54..5edb26977fe7 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Картинка в картинці"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без назви)"</string> - <string name="pip_close" msgid="9135220303720555525">"Закрити PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Закрити"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"На весь екран"</string> - <string name="pip_move" msgid="1544227837964635439">"Перемістити картинку в картинці"</string> - <string name="pip_expand" msgid="7605396312689038178">"Розгорнути картинку в картинці"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Згорнути картинку в картинці"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Відкрити елементи керування: двічі натисніть "<annotation icon="home_icon">"HOME"</annotation></string> + <string name="pip_move" msgid="158770205886688553">"Перемістити"</string> + <string name="pip_expand" msgid="1051966011679297308">"Розгорнути"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Згорнути"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Відкрити елементи керування: двічі натисніть "<annotation icon="home_icon">"HOME"</annotation></string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Меню \"картинка в картинці\""</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Перемістити ліворуч"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Перемістити праворуч"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Перемістити вгору"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Перемістити вниз"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Готово"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index a31c2be25643..81672bff7545 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"ترتیبات"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"اسپلٹ اسکرین تک رسائی"</string> <string name="pip_menu_title" msgid="5393619322111827096">"مینو"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"تصویر میں تصویر کا مینو"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> تصویر میں تصویر میں ہے"</string> <string name="pip_notification_message" msgid="8854051911700302620">"اگر آپ نہیں چاہتے ہیں کہ <xliff:g id="NAME">%s</xliff:g> اس خصوصیت کا استعمال کرے تو ترتیبات کھولنے کے لیے تھپتھپا کر اسے آف کرے۔"</string> <string name="pip_play" msgid="3496151081459417097">"چلائیں"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے۔"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ایپ سپلٹ اسکرین کو سپورٹ نہیں کرتی۔"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے۔"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ایپ ثانوی ڈسپلیز پر شروعات کا تعاون نہیں کرتی۔"</string> <string name="accessibility_divider" msgid="703810061635792791">"سپلٹ اسکرین تقسیم کار"</string> + <string name="divider_title" msgid="5482989479865361192">"اسپلٹ اسکرین ڈیوائیڈر"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"بائیں فل اسکرین"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"بائیں %70"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"بائیں %50"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"بلبلہ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"نظم کریں"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"بلبلہ برخاست کر دیا گیا۔"</string> - <string name="restart_button_description" msgid="5887656107651190519">"یہ ایپ دوبارہ شروع کرنے کے لیے تھپتھپائیں اور پوری اسکرین پر جائیں۔"</string> + <string name="restart_button_description" msgid="6712141648865547958">"بہتر منظر کے لیے اس ایپ کو ری اسٹارٹ کرنے کی خاطر تھپتھپائیں۔"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"کچھ ایپس پورٹریٹ میں بہترین کام کرتی ہیں"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"اپنی اسپیس کا زیادہ سے زیادہ فائدہ اٹھانے کے لیے ان اختیارات میں سے ایک کو آزمائیں"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"پوری اسکرین پر جانے کیلئے اپنا آلہ گھمائیں"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس کے آگے دو بار تھپتھپائیں"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"دیکھیں اور بہت کچھ کریں"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس ایپ کے باہر دو بار تھپتھپائیں"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"سمجھ آ گئی"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"مزید معلومات کے لیے پھیلائیں۔"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string> + <string name="minimize_button_text" msgid="271592547935841753">"چھوٹا کریں"</string> + <string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string> + <string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string> + <string name="handle_text" msgid="1766582106752184456">"ہینڈل"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"مکمل اسکرین"</string> + <string name="desktop_text" msgid="1077633567027630454">"ڈیسک ٹاپ موڈ"</string> + <string name="split_screen_text" msgid="1396336058129570886">"اسپلٹ اسکرین"</string> + <string name="more_button_text" msgid="3655388105592893530">"مزید"</string> + <string name="float_button_text" msgid="9221657008391364581">"فلوٹ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml index c2ef69ff1488..42b9564ff549 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"تصویر میں تصویر"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(بلا عنوان پروگرام)"</string> - <string name="pip_close" msgid="9135220303720555525">"PIP بند کریں"</string> + <string name="pip_close" msgid="2955969519031223530">"بند کریں"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"فُل اسکرین"</string> - <string name="pip_move" msgid="1544227837964635439">"PIP کو منتقل کریں"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP کو پھیلائیں"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP کو سکیڑیں"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" کنٹرولز کے لیے "<annotation icon="home_icon">"ہوم "</annotation>" بٹن کو دو بار دبائیں"</string> + <string name="pip_move" msgid="158770205886688553">"منتقل کریں"</string> + <string name="pip_expand" msgid="1051966011679297308">"پھیلائیں"</string> + <string name="pip_collapse" msgid="3903295106641385962">"سکیڑیں"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"کنٹرولز کے لیے "<annotation icon="home_icon">"ہوم "</annotation>" کو دو بار دبائیں"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"تصویر میں تصویر کا مینو۔"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"دائیں منتقل کریں"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"بائیں منتقل کریں"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"اوپر منتقل کریں"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"نیچے منتقل کریں"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ہو گیا"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index 2e3222560dde..d0384e944248 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Sozlamalar"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Ajratilgan ekranga kirish"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Tasvir ustida tasvir menyusi"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> tasvir ustida tasvir rejimida"</string> <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ilovasi uchun bu funksiyani sozlamalar orqali faolsizlantirish mumkin."</string> <string name="pip_play" msgid="3496151081459417097">"Ijro"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Chiqarish"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Bu ilova ekranni ikkiga ajratish rejimini dastaklamaydi."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Bu ilova ekranni bo‘lish xususiyatini qo‘llab-quvvatlamaydi."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu ilovani faqat 1 ta oynada ochish mumkin."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Bu ilova qo‘shimcha ekranlarda ishga tushmaydi."</string> <string name="accessibility_divider" msgid="703810061635792791">"Ekranni ikkiga bo‘lish chizig‘i"</string> + <string name="divider_title" msgid="5482989479865361192">"Ekranni ikkiga ajratish chizigʻi"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Chapda to‘liq ekran"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Chapda 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Chapda 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Pufaklar"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Boshqarish"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulutcha yopildi."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Bu ilovani qaytadan ishga tushirish va butun ekranda ochish uchun bosing."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Yaxshiroq koʻrish maqsadida bu ilovani qayta ishga tushirish uchun bosing."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera nosozmi?\nQayta moslash uchun bosing"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tuzatilmadimi?\nQaytarish uchun bosing"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera muammosizmi? Yopish uchun bosing."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Ayrim ilovalar tik holatda ishlashga eng mos"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Muhitdan yanada samarali foydalanish uchun quyidagilardan birini sinang"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Butun ekranda ochish uchun qurilmani buring"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Qayta joylash uchun keyingi ilova ustiga ikki marta bosing"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Yana boshqa amallar"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Ekranni ikkiga ajratish uchun boshqa ilovani bu yerga torting"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Qayta joylash uchun ilova tashqarisiga ikki marta bosing"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Kichraytirish"</string> + <string name="close_button_text" msgid="2913281996024033299">"Yopish"</string> + <string name="back_button_text" msgid="1469718707134137085">"Orqaga"</string> + <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Butun ekran"</string> + <string name="desktop_text" msgid="1077633567027630454">"Desktop rejimi"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Ekranni ikkiga ajratish"</string> + <string name="more_button_text" msgid="3655388105592893530">"Yana"</string> + <string name="float_button_text" msgid="9221657008391364581">"Pufakli"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml index 9ab95c80aa25..83fd8b4e5425 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Tasvir ustida tasvir"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nomsiz)"</string> - <string name="pip_close" msgid="9135220303720555525">"Kadr ichida kadr – chiqish"</string> + <string name="pip_close" msgid="2955969519031223530">"Yopish"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Butun ekran"</string> - <string name="pip_move" msgid="1544227837964635439">"PIPni siljitish"</string> - <string name="pip_expand" msgid="7605396312689038178">"PIP funksiyasini yoyish"</string> - <string name="pip_collapse" msgid="5732233773786896094">"PIP funksiyasini yopish"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Boshqaruv uchun "<annotation icon="home_icon">"ASOSIY"</annotation>" tugmani ikki marta bosing"</string> + <string name="pip_move" msgid="158770205886688553">"Boshqa joyga olish"</string> + <string name="pip_expand" msgid="1051966011679297308">"Yoyish"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Yopish"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Boshqaruv uchun "<annotation icon="home_icon">"ASOSIY"</annotation>" tugmani ikki marta bosing"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Tasvir ustida tasvir menyusi."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Chapga olish"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Oʻngga olish"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Tepaga olish"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pastga olish"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Tayyor"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index 8f3cffecc952..49986b591e28 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"Cài đặt"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Truy cập chế độ chia đôi màn hình"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Trình đơn hình trong hình."</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> đang ở chế độ ảnh trong ảnh"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Nếu bạn không muốn <xliff:g id="NAME">%s</xliff:g> sử dụng tính năng này, hãy nhấn để mở cài đặt và tắt tính năng này."</string> <string name="pip_play" msgid="3496151081459417097">"Phát"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Hiện"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Ứng dụng có thể không hoạt động với tính năng chia đôi màn hình."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Ứng dụng không hỗ trợ chia đôi màn hình."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ứng dụng này chỉ có thể mở 1 cửa sổ."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Ứng dụng có thể không hoạt động trên màn hình phụ."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Ứng dụng không hỗ trợ khởi chạy trên màn hình phụ."</string> <string name="accessibility_divider" msgid="703810061635792791">"Bộ chia chia đôi màn hình"</string> + <string name="divider_title" msgid="5482989479865361192">"Bộ chia màn hình"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Toàn màn hình bên trái"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Trái 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Trái 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bong bóng"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Quản lý"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Đã đóng bong bóng."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Nhấn để khởi động lại ứng dụng này và xem ở chế độ toàn màn hình."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Nhấn để khởi động lại ứng dụng này để xem tốt hơn."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Có vấn đề với máy ảnh?\nHãy nhấn để sửa lỗi"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bạn chưa khắc phục vấn đề?\nHãy nhấn để hủy bỏ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Không có vấn đề với máy ảnh? Hãy nhấn để đóng."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Một số ứng dụng hoạt động tốt nhất ở chế độ dọc"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Hãy thử một trong các tuỳ chọn sau để tận dụng không gian"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Xoay thiết bị để chuyển sang chế độ toàn màn hình"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Nhấn đúp vào bên cạnh ứng dụng để đặt lại vị trí"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Xem và làm được nhiều việc hơn"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Kéo vào một ứng dụng khác để chia đôi màn hình"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Nhấn đúp bên ngoài ứng dụng để đặt lại vị trí"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Thu nhỏ"</string> + <string name="close_button_text" msgid="2913281996024033299">"Đóng"</string> + <string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string> + <string name="handle_text" msgid="1766582106752184456">"Xử lý"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Toàn màn hình"</string> + <string name="desktop_text" msgid="1077633567027630454">"Chế độ máy tính"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Chia đôi màn hình"</string> + <string name="more_button_text" msgid="3655388105592893530">"Tuỳ chọn khác"</string> + <string name="float_button_text" msgid="9221657008391364581">"Nổi"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml index 146376d3cab6..986690f0444c 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Hình trong hình"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Không có chương trình tiêu đề)"</string> - <string name="pip_close" msgid="9135220303720555525">"Đóng PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Đóng"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Toàn màn hình"</string> - <string name="pip_move" msgid="1544227837964635439">"Di chuyển PIP (Ảnh trong ảnh)"</string> - <string name="pip_expand" msgid="7605396312689038178">"Mở rộng PIP (Ảnh trong ảnh)"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Thu gọn PIP (Ảnh trong ảnh)"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Nhấn đúp vào nút "<annotation icon="home_icon">" MÀN HÌNH CHÍNH "</annotation>" để mở trình đơn điều khiển"</string> + <string name="pip_move" msgid="158770205886688553">"Di chuyển"</string> + <string name="pip_expand" msgid="1051966011679297308">"Mở rộng"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Thu gọn"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Nhấn đúp vào nút "<annotation icon="home_icon">"MÀN HÌNH CHÍNH"</annotation>" để mở trình đơn điều khiển"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Trình đơn hình trong hình."</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Di chuyển sang trái"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Di chuyển sang phải"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Di chuyển lên"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Di chuyển xuống"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Xong"</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 19a9d371e435..acdb2523597d 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"设置"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"进入分屏模式"</string> <string name="pip_menu_title" msgid="5393619322111827096">"菜单"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"画中画菜单"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>目前位于“画中画”中"</string> <string name="pip_notification_message" msgid="8854051911700302620">"如果您不想让“<xliff:g id="NAME">%s</xliff:g>”使用此功能,请点按以打开设置,然后关闭此功能。"</string> <string name="pip_play" msgid="3496151081459417097">"播放"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消隐藏"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"应用可能无法在分屏模式下正常运行。"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"应用不支持分屏。"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此应用只能在 1 个窗口中打开。"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"应用可能无法在辅显示屏上正常运行。"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"应用不支持在辅显示屏上启动。"</string> <string name="accessibility_divider" msgid="703810061635792791">"分屏分隔线"</string> + <string name="divider_title" msgid="5482989479865361192">"分屏分隔线"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"左侧全屏"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"左侧 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左侧 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"气泡"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已关闭对话泡。"</string> - <string name="restart_button_description" msgid="5887656107651190519">"点按即可重启此应用并进入全屏模式。"</string> + <string name="restart_button_description" msgid="6712141648865547958">"点按即可重启此应用,获得更好的视图体验。"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"某些应用在纵向模式下才能发挥最佳效果"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"这些选项都有助于您最大限度地利用屏幕空间,不妨从中择一试试"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋转设备即可进入全屏模式"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在某个应用旁边连续点按两次,即可调整它的位置"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"查看和处理更多任务"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一个应用,即可使用分屏模式"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在某个应用外连续点按两次,即可调整它的位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展开即可了解详情。"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> + <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> + <string name="close_button_text" msgid="2913281996024033299">"关闭"</string> + <string name="back_button_text" msgid="1469718707134137085">"返回"</string> + <string name="handle_text" msgid="1766582106752184456">"处理"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"全屏"</string> + <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string> + <string name="split_screen_text" msgid="1396336058129570886">"分屏"</string> + <string name="more_button_text" msgid="3655388105592893530">"更多"</string> + <string name="float_button_text" msgid="9221657008391364581">"悬浮"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml index 55407d2c699d..4da96e89f136 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"画中画"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(节目没有标题)"</string> - <string name="pip_close" msgid="9135220303720555525">"关闭画中画"</string> + <string name="pip_close" msgid="2955969519031223530">"关闭"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全屏"</string> - <string name="pip_move" msgid="1544227837964635439">"移动画中画窗口"</string> - <string name="pip_expand" msgid="7605396312689038178">"展开 PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"收起 PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" 按两次"<annotation icon="home_icon">"主屏幕"</annotation>"按钮可查看相关控件"</string> + <string name="pip_move" msgid="158770205886688553">"移动"</string> + <string name="pip_expand" msgid="1051966011679297308">"展开"</string> + <string name="pip_collapse" msgid="3903295106641385962">"收起"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"按两次"<annotation icon="home_icon">"主屏幕"</annotation>"按钮可查看相关控件"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"画中画菜单。"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"左移"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"右移"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"上移"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"下移"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"完成"</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 0c40e963f2e4..b1a957e5c5cf 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"進入分割螢幕"</string> <string name="pip_menu_title" msgid="5393619322111827096">"選單"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"畫中畫選單"</string> <string name="pip_notification_title" msgid="1347104727641353453">"「<xliff:g id="NAME">%s</xliff:g>」目前在畫中畫模式"</string> <string name="pip_notification_message" msgid="8854051911700302620">"如果您不想「<xliff:g id="NAME">%s</xliff:g>」使用此功能,請輕按以開啟設定,然後停用此功能。"</string> <string name="pip_play" msgid="3496151081459417097">"播放"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消保護"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"應用程式不支援分割畫面。"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此應用程式只可在 1 個視窗中開啟"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示屏上運作。"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示屏上啟動。"</string> <string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string> + <string name="divider_title" msgid="5482989479865361192">"分割螢幕分隔線"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"左邊全螢幕"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"左邊 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左邊 50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"氣泡"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"對話氣泡已關閉。"</string> - <string name="restart_button_description" msgid="5887656107651190519">"輕按即可重新開啟此應用程式並放大至全螢幕。"</string> + <string name="restart_button_description" msgid="6712141648865547958">"輕按並重新啟動此應用程式,以取得更佳的觀看體驗。"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"部分應用程式需要使用直向模式才能發揮最佳效果"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"請嘗試以下選項,充分運用螢幕的畫面空間"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋轉裝置方向即可進入全螢幕模式"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在應用程式旁輕按兩下即可調整位置"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一個應用程式即可分割螢幕"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕按兩下即可調整位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> + <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> + <string name="close_button_text" msgid="2913281996024033299">"關閉"</string> + <string name="back_button_text" msgid="1469718707134137085">"返去"</string> + <string name="handle_text" msgid="1766582106752184456">"控點"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string> + <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string> + <string name="split_screen_text" msgid="1396336058129570886">"分割螢幕"</string> + <string name="more_button_text" msgid="3655388105592893530">"更多"</string> + <string name="float_button_text" msgid="9221657008391364581">"浮動"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml index 15e278d8ecc2..ce850ef3c675 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"畫中畫"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(沒有標題的節目)"</string> - <string name="pip_close" msgid="9135220303720555525">"關閉 PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"關閉"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string> - <string name="pip_move" msgid="1544227837964635439">"移動畫中畫"</string> - <string name="pip_expand" msgid="7605396312689038178">"展開畫中畫"</string> - <string name="pip_collapse" msgid="5732233773786896094">"收合畫中畫"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" 按兩下"<annotation icon="home_icon">" 主畫面按鈕"</annotation>"即可顯示控制項"</string> + <string name="pip_move" msgid="158770205886688553">"移動"</string> + <string name="pip_expand" msgid="1051966011679297308">"展開"</string> + <string name="pip_collapse" msgid="3903295106641385962">"收合"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"按兩下"<annotation icon="home_icon">" 主畫面按鈕"</annotation>"即可存取控制項"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"畫中畫選單。"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"向左移"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"向右移"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"向上移"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"向下移"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"完成"</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 8691352cf94a..bb3dba17abc7 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -22,6 +22,7 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"進入分割畫面"</string> <string name="pip_menu_title" msgid="5393619322111827096">"選單"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"子母畫面選單"</string> <string name="pip_notification_title" msgid="1347104727641353453">"「<xliff:g id="NAME">%s</xliff:g>」目前在子母畫面中"</string> <string name="pip_notification_message" msgid="8854051911700302620">"如果你不想讓「<xliff:g id="NAME">%s</xliff:g>」使用這項功能,請輕觸開啟設定頁面,然後停用此功能。"</string> <string name="pip_play" msgid="3496151081459417097">"播放"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消暫時隱藏"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"這個應用程式不支援分割畫面。"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"這個應用程式只能在 1 個視窗中開啟。"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示器上運作。"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示器上啟動。"</string> <string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string> + <string name="divider_title" msgid="5482989479865361192">"分割畫面分隔線"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"以全螢幕顯示左側畫面"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"以 70% 的螢幕空間顯示左側畫面"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"以 50% 的螢幕空間顯示左側畫面"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"泡泡"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已關閉泡泡。"</string> - <string name="restart_button_description" msgid="5887656107651190519">"輕觸即可重新啟動這個應用程式並進入全螢幕模式。"</string> + <string name="restart_button_description" msgid="6712141648865547958">"請輕觸並重新啟動此應用程式,取得更良好的觀看體驗。"</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> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"某些應用程式在直向模式下才能發揮最佳效果"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"請試試這裡的任一方式,以充分運用螢幕畫面的空間"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋轉裝置方向即可進入全螢幕模式"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在應用程式旁輕觸兩下即可調整位置"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖進另一個應用程式即可使用分割畫面模式"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕觸兩下即可調整位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"我知道了"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳細資訊。"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> + <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> + <string name="close_button_text" msgid="2913281996024033299">"關閉"</string> + <string name="back_button_text" msgid="1469718707134137085">"返回"</string> + <string name="handle_text" msgid="1766582106752184456">"控點"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string> + <string name="desktop_text" msgid="1077633567027630454">"電腦模式"</string> + <string name="split_screen_text" msgid="1396336058129570886">"分割畫面"</string> + <string name="more_button_text" msgid="3655388105592893530">"更多"</string> + <string name="float_button_text" msgid="9221657008391364581">"浮動"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml index 0b17b31d23d0..df870851e72b 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"子母畫面"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無標題的節目)"</string> - <string name="pip_close" msgid="9135220303720555525">"關閉子母畫面"</string> + <string name="pip_close" msgid="2955969519031223530">"關閉"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string> - <string name="pip_move" msgid="1544227837964635439">"移動子母畫面"</string> - <string name="pip_expand" msgid="7605396312689038178">"展開子母畫面"</string> - <string name="pip_collapse" msgid="5732233773786896094">"收合子母畫面"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" 按兩下"<annotation icon="home_icon">"主畫面按鈕"</annotation>"即可顯示控制選項"</string> + <string name="pip_move" msgid="158770205886688553">"移動"</string> + <string name="pip_expand" msgid="1051966011679297308">"展開"</string> + <string name="pip_collapse" msgid="3903295106641385962">"收合"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"按兩下"<annotation icon="home_icon">"主畫面按鈕"</annotation>"即可存取控制選項"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"子母畫面選單。"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"向左移動"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"向右移動"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"向上移動"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"向下移動"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"完成"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index 44ffbc6afa45..51a23ff64403 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -19,9 +19,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="pip_phone_close" msgid="5783752637260411309">"Vala"</string> <string name="pip_phone_expand" msgid="2579292903468287504">"Nweba"</string> - <string name="pip_phone_settings" msgid="5468987116750491918">"Izilungiselelo"</string> + <string name="pip_phone_settings" msgid="5468987116750491918">"Amasethingi"</string> <string name="pip_phone_enter_split" msgid="7042877263880641911">"Faka ukuhlukanisa isikrini"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Imenyu"</string> + <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Imenyu Yesithombe-Esithombeni"</string> <string name="pip_notification_title" msgid="1347104727641353453">"U-<xliff:g id="NAME">%s</xliff:g> ungaphakathi kwesithombe esiphakathi kwesithombe"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Uma ungafuni i-<xliff:g id="NAME">%s</xliff:g> ukuthi isebenzise lesi sici, thepha ukuze uvule izilungiselelo uphinde uyivale."</string> <string name="pip_play" msgid="3496151081459417097">"Dlala"</string> @@ -33,9 +34,11 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Susa isiteshi"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Izinhlelo zokusebenza kungenzeka zingasebenzi ngesikrini esihlukanisiwe."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uhlelo lokusebenza alusekeli isikrini esihlukanisiwe."</string> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Le-app ingavulwa kuphela ewindini eli-1."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uhlelo lokusebenza alusekeli ukuqalisa kuzibonisi zesibili."</string> <string name="accessibility_divider" msgid="703810061635792791">"Isihlukanisi sokuhlukanisa isikrini"</string> + <string name="divider_title" msgid="5482989479865361192">"Isihlukanisi sokuhlukanisa isikrini"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Isikrini esigcwele esingakwesokunxele"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kwesokunxele ngo-70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kwesokunxele ngo-50%"</string> @@ -72,13 +75,23 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Ibhamuza"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Phatha"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ibhamuza licashisiwe."</string> - <string name="restart_button_description" msgid="5887656107651190519">"Thepha ukuze uqale kabusha lolu hlelo lokusebenza uphinde uye kusikrini esigcwele."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Thepha ukuze uqale kabusha le app ukuze ibonakale kangcono."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Izinkinga zekhamera?\nThepha ukuze uyilinganise kabusha"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Akuyilungisanga?\nThepha ukuze ubuyele"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Azikho izinkinga zekhamera? Thepha ukuze ucashise."</string> - <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Amanye ama-app asebenza ngcono uma eme ngobude"</string> - <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Zama enye yalezi zinketho ukuze usebenzise isikhala sakho ngokugcwele"</string> - <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zungezisa idivayisi yakho ukuze uye esikrinini esigcwele"</string> - <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Thepha kabili eduze kwe-app ukuze uyimise kabusha"</string> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Bona futhi wenze okuningi"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Hudula kwenye i-app mayelana nokuhlukanisa isikrini"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Thepha kabili ngaphandle kwe-app ukuze uyimise kabusha"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Nweba ukuze uthole ulwazi olwengeziwe"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string> + <string name="minimize_button_text" msgid="271592547935841753">"Nciphisa"</string> + <string name="close_button_text" msgid="2913281996024033299">"Vala"</string> + <string name="back_button_text" msgid="1469718707134137085">"Emuva"</string> + <string name="handle_text" msgid="1766582106752184456">"Isibambo"</string> + <string name="fullscreen_text" msgid="1162316685217676079">"Isikrini esigcwele"</string> + <string name="desktop_text" msgid="1077633567027630454">"Imodi Yedeskithophu"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Hlukanisa isikrini"</string> + <string name="more_button_text" msgid="3655388105592893530">"Okwengeziwe"</string> + <string name="float_button_text" msgid="9221657008391364581">"Iflowuthi"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml index dad8c8128222..34cc8f1f1647 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml @@ -19,10 +19,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Isithombe-esithombeni"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Alukho uhlelo lwesihloko)"</string> - <string name="pip_close" msgid="9135220303720555525">"Vala i-PIP"</string> + <string name="pip_close" msgid="2955969519031223530">"Vala"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Iskrini esigcwele"</string> - <string name="pip_move" msgid="1544227837964635439">"Hambisa i-PIP"</string> - <string name="pip_expand" msgid="7605396312689038178">"Nweba i-PIP"</string> - <string name="pip_collapse" msgid="5732233773786896094">"Goqa i-PIP"</string> - <string name="pip_edu_text" msgid="3672999496647508701">" Chofoza kabili "<annotation icon="home_icon">" IKHAYA"</annotation>" mayelana nezilawuli"</string> + <string name="pip_move" msgid="158770205886688553">"Hambisa"</string> + <string name="pip_expand" msgid="1051966011679297308">"Nweba"</string> + <string name="pip_collapse" msgid="3903295106641385962">"Goqa"</string> + <string name="pip_edu_text" msgid="7930546669915337998">"Chofoza kabili "<annotation icon="home_icon">" IKHAYA"</annotation>" mayelana nezilawuli"</string> + <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Imenyu yesithombe-esithombeni"</string> + <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Yisa kwesokunxele"</string> + <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Yisa kwesokudla"</string> + <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Khuphula"</string> + <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Yehlisa"</string> + <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Kwenziwe"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml index fa90fe36b545..e6933ca3fce6 100644 --- a/libs/WindowManager/Shell/res/values/colors_tv.xml +++ b/libs/WindowManager/Shell/res/values/colors_tv.xml @@ -15,14 +15,17 @@ ~ limitations under the License. --> <resources> - <color name="tv_pip_menu_icon_focused">#0E0E0F</color> - <color name="tv_pip_menu_icon_unfocused">#F8F9FA</color> - <color name="tv_pip_menu_icon_disabled">#80868B</color> - <color name="tv_pip_menu_close_icon_bg_focused">#D93025</color> - <color name="tv_pip_menu_close_icon_bg_unfocused">#D69F261F</color> - <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color> - <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color> + <color name="tv_window_menu_icon_focused">#0E0E0F</color> + <color name="tv_window_menu_icon_unfocused">#F8F9FA</color> + + <color name="tv_window_menu_icon_disabled">#80868B</color> + <color name="tv_window_menu_close_icon_bg_focused">#D93025</color> + <color name="tv_window_menu_close_icon_bg_unfocused">#D69F261F</color> + <color name="tv_window_menu_icon_bg_focused">#E8EAED</color> + <color name="tv_window_menu_icon_bg_unfocused">#990E0E0F</color> + <color name="tv_pip_menu_focus_border">#E8EAED</color> + <color name="tv_pip_menu_dim_layer">#990E0E0F</color> <color name="tv_pip_menu_background">#1E232C</color> <color name="tv_pip_edu_text">#99D2E3FC</color> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 25eddf834f3d..9490ddc73488 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -30,6 +30,9 @@ <!-- Title of menu shown over picture-in-picture. Used for accessibility. --> <string name="pip_menu_title">Menu</string> + <!-- accessibility Title of menu shown over picture-in-picture [CHAR LIMIT=NONE] --> + <string name="pip_menu_accessibility_title">Picture-in-Picture Menu</string> + <!-- PiP BTW notification title. [CHAR LIMIT=50] --> <string name="pip_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> is in picture-in-picture</string> @@ -75,8 +78,10 @@ <!-- Warning message when we try to launch a non-resizeable activity on a secondary display and launch it on the primary instead. --> <string name="activity_launch_on_secondary_display_failed_text">App does not support launch on secondary displays.</string> - <!-- Accessibility label for the divider that separates the windows in split-screen mode [CHAR LIMIT=NONE] --> + <!-- Accessibility label and window tile for the divider that separates the windows in split-screen mode [CHAR LIMIT=NONE] --> <string name="accessibility_divider">Split-screen divider</string> + <!-- Accessibility window title for the split-screen divider window [CHAR LIMIT=NONE] --> + <string name="divider_title">Split-screen divider</string> <!-- Accessibility action for moving docked stack divider to make the left screen full screen [CHAR LIMIT=NONE] --> <string name="accessibility_action_divider_left_full">Left full screen</string> diff --git a/libs/WindowManager/Shell/res/values/strings_tv.xml b/libs/WindowManager/Shell/res/values/strings_tv.xml index 2b7a13eac6ca..8f806cf56c9b 100644 --- a/libs/WindowManager/Shell/res/values/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values/strings_tv.xml @@ -42,8 +42,8 @@ <!-- Educative text instructing the user to double press the HOME button to access the pip controls menu [CHAR LIMIT=50] --> - <string name="pip_edu_text"> Double press <annotation icon="home_icon"> HOME </annotation> for - controls </string> + <string name="pip_edu_text">Double press <annotation icon="home_icon">HOME</annotation> for + controls</string> <!-- Accessibility announcement when opening the PiP menu. [CHAR LIMIT=NONE] --> <string name="a11y_pip_menu_entered">Picture-in-Picture menu.</string> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 97a9fede22d5..b5ef72aec6aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -42,6 +42,7 @@ import android.util.Log; import android.util.SparseArray; import android.view.SurfaceControl; import android.window.ITaskOrganizerController; +import android.window.ScreenCapture; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; import android.window.TaskAppearedInfo; @@ -489,7 +490,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements * Take a screenshot of a task. */ public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, - Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) { + Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) { final TaskAppearedInfo info = mTasks.get(taskInfo.taskId); if (info == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/TEST_MAPPING new file mode 100644 index 000000000000..8dd1369ecbb2 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "ironwood-postsubmit": [ + { + "name": "WMShellFlickerTests", + "options": [ + { + "include-annotation": "android.platform.test.annotations.IwTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index 95a89b1d23ff..94e01e96730c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -108,7 +108,6 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, if (mTaskViewTransitions != null) { mTaskViewTransitions.addTaskView(this); } - setUseAlpha(); getHolder().addCallback(this); mGuard.open("release"); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java index a0dde6ad168d..2ec9e8b12fc6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java @@ -16,6 +16,7 @@ package com.android.wm.shell.animation; +import android.graphics.Path; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.PathInterpolator; @@ -53,6 +54,11 @@ public class Interpolators { public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f); /** + * The default emphasized interpolator. Used for hero / emphasized movement of content. + */ + public static final Interpolator EMPHASIZED = createEmphasizedInterpolator(); + + /** * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that * is disappearing e.g. when moving off screen. */ @@ -81,4 +87,14 @@ public class Interpolators { public static final PathInterpolator DIM_INTERPOLATOR = new PathInterpolator(.23f, .87f, .52f, -0.11f); + + // Create the default emphasized interpolator + private static PathInterpolator createEmphasizedInterpolator() { + Path path = new Path(); + // Doing the same as fast_out_extra_slow_in + path.moveTo(0f, 0f); + path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f); + path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f); + return new PathInterpolator(path); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java new file mode 100644 index 000000000000..36cf29a4c4f3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 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.back; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.annotation.NonNull; +import android.graphics.Color; +import android.view.SurfaceControl; + +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; + +/** + * Controls background surface for the back animations + */ +public class BackAnimationBackground { + private static final int BACKGROUND_LAYER = -1; + private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private SurfaceControl mBackgroundSurface; + + public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; + } + + void ensureBackground(int color, @NonNull SurfaceControl.Transaction transaction) { + if (mBackgroundSurface != null) { + return; + } + + final float[] colorComponents = new float[] { Color.red(color) / 255.f, + Color.green(color) / 255.f, Color.blue(color) / 255.f }; + + final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder() + .setName("back-animation-background") + .setCallsite("BackAnimationBackground") + .setColorLayer(); + + mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder); + mBackgroundSurface = colorLayerBuilder.build(); + transaction.setColor(mBackgroundSurface, colorComponents) + .setLayer(mBackgroundSurface, BACKGROUND_LAYER) + .show(mBackgroundSurface); + } + + void removeBackground(@NonNull SurfaceControl.Transaction transaction) { + if (mBackgroundSurface == null) { + return; + } + + if (mBackgroundSurface.isValid()) { + transaction.remove(mBackgroundSurface); + } + mBackgroundSurface = null; + } +} 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 236309207b4f..57a0fd593551 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 @@ -16,9 +16,6 @@ package com.android.wm.shell.back; -import static android.view.RemoteAnimationTarget.MODE_CLOSING; -import static android.view.RemoteAnimationTarget.MODE_OPENING; - import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; @@ -27,11 +24,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; -import android.app.WindowConfiguration; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; -import android.hardware.HardwareBuffer; import android.hardware.input.InputManager; import android.net.Uri; import android.os.Handler; @@ -42,19 +37,20 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings.Global; import android.util.Log; +import android.util.SparseArray; +import android.view.IRemoteAnimationRunner; import android.view.IWindowFocusObserver; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.window.BackAnimationAdaptor; +import android.window.BackAnimationAdapter; import android.window.BackEvent; import android.window.BackMotionEvent; import android.window.BackNavigationInfo; +import android.window.IBackAnimationFinishedCallback; import android.window.IBackAnimationRunner; -import android.window.IBackNaviAnimationController; import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; @@ -73,32 +69,28 @@ import java.util.concurrent.atomic.AtomicBoolean; * Controls the window animation run when a user initiates a back gesture. */ public class BackAnimationController implements RemoteCallable<BackAnimationController> { - private static final String TAG = "BackAnimationController"; + private static final String TAG = "ShellBackPreview"; private static final int SETTING_VALUE_OFF = 0; private static final int SETTING_VALUE_ON = 1; public static final boolean IS_ENABLED = SystemProperties.getInt("persist.wm.debug.predictive_back", SETTING_VALUE_ON) == SETTING_VALUE_ON; - /** Flag for U animation features */ + /** Flag for U animation features */ public static boolean IS_U_ANIMATION_ENABLED = SystemProperties.getInt("persist.wm.debug.predictive_back_anim", SETTING_VALUE_OFF) == SETTING_VALUE_ON; /** Predictive back animation developer option */ private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); - // TODO (b/241808055) Find a appropriate time to remove during refactor - private static final boolean USE_TRANSITION = - SystemProperties.getInt("persist.wm.debug.predictive_back_ani_trans", 1) != 0; /** - * Max duration to wait for a transition to finish before accepting another gesture start - * request. + * Max duration to wait for an animation to finish before triggering the real back. */ - private static final long MAX_TRANSITION_DURATION = 2000; + private static final long MAX_ANIMATION_DURATION = 2000; /** True when a back gesture is ongoing */ private boolean mBackGestureStarted = false; - /** Tracks if an uninterruptible transition is in progress */ - private boolean mTransitionInProgress = false; + /** Tracks if an uninterruptible animation is in progress */ + private boolean mPostCommitAnimationInProgress = false; /** Tracks if we should start the back gesture on the next motion move event */ private boolean mShouldStartOnNextMoveEvent = false; /** @see #setTriggerBack(boolean) */ @@ -106,28 +98,27 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @Nullable private BackNavigationInfo mBackNavigationInfo; - private final SurfaceControl.Transaction mTransaction; private final IActivityTaskManager mActivityTaskManager; private final Context mContext; private final ContentResolver mContentResolver; private final ShellController mShellController; private final ShellExecutor mShellExecutor; private final Handler mBgHandler; - @Nullable - private IOnBackInvokedCallback mBackToLauncherCallback; - private float mTriggerThreshold; - private final Runnable mResetTransitionRunnable = () -> { - finishAnimation(); - mTransitionInProgress = false; + private final Runnable mAnimationTimeoutRunnable = () -> { + ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...", + MAX_ANIMATION_DURATION); + onBackAnimationFinished(); }; - private RemoteAnimationTarget mAnimationTarget; - IBackAnimationRunner mIBackAnimationRunner; - private IBackNaviAnimationController mBackAnimationController; - private BackAnimationAdaptor mBackAnimationAdaptor; + private IBackAnimationFinishedCallback mBackAnimationFinishedCallback; + @VisibleForTesting + BackAnimationAdapter mBackAnimationAdapter; private final TouchTracker mTouchTracker = new TouchTracker(); - private final CachingBackDispatcher mCachingBackDispatcher = new CachingBackDispatcher(); + + private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>(); + @Nullable + private IOnBackInvokedCallback mActiveCallback; @VisibleForTesting final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() { @@ -136,70 +127,30 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @Override public void focusLost(IBinder inputToken) { mShellExecutor.execute(() -> { - if (!mBackGestureStarted || mTransitionInProgress) { - // If an uninterruptible transition is already in progress, we should ignore - // this due to the transition may cause focus lost. (alpha = 0) + if (!mBackGestureStarted || mPostCommitAnimationInProgress) { + // If an uninterruptible animation is already in progress, we should ignore + // this due to it may cause focus lost. (alpha = 0) return; } + ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus."); setTriggerBack(false); onGestureFinished(false); }); } }; - /** - * Cache the temporary callback and trigger result if gesture was finish before received - * BackAnimationRunner#onAnimationStart/cancel, so there can continue play the animation. - */ - private class CachingBackDispatcher { - private IOnBackInvokedCallback mOnBackCallback; - private boolean mTriggerBack; - // Whether we are waiting to receive onAnimationStart - private boolean mWaitingAnimation; - - void startWaitingAnimation() { - mWaitingAnimation = true; - } - - boolean set(IOnBackInvokedCallback callback, boolean triggerBack) { - if (mWaitingAnimation) { - mOnBackCallback = callback; - mTriggerBack = triggerBack; - return true; - } - return false; - } - - boolean consume() { - boolean consumed = false; - if (mWaitingAnimation && mOnBackCallback != null) { - if (mTriggerBack) { - final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(1); - dispatchOnBackProgressed(mBackToLauncherCallback, backFinish); - dispatchOnBackInvoked(mOnBackCallback); - } else { - final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(0); - dispatchOnBackProgressed(mBackToLauncherCallback, backFinish); - dispatchOnBackCancelled(mOnBackCallback); - } - startTransition(); - consumed = true; - } - mOnBackCallback = null; - mWaitingAnimation = false; - return consumed; - } - } + private final BackAnimationBackground mAnimationBackground; public BackAnimationController( @NonNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler backgroundHandler, - Context context) { + Context context, + @NonNull BackAnimationBackground backAnimationBackground) { this(shellInit, shellController, shellExecutor, backgroundHandler, - new SurfaceControl.Transaction(), ActivityTaskManager.getService(), - context, context.getContentResolver()); + ActivityTaskManager.getService(), context, context.getContentResolver(), + backAnimationBackground); } @VisibleForTesting @@ -208,17 +159,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler bgHandler, - @NonNull SurfaceControl.Transaction transaction, @NonNull IActivityTaskManager activityTaskManager, - Context context, ContentResolver contentResolver) { + Context context, ContentResolver contentResolver, + @NonNull BackAnimationBackground backAnimationBackground) { mShellController = shellController; mShellExecutor = shellExecutor; - mTransaction = transaction; mActivityTaskManager = activityTaskManager; mContext = context; mContentResolver = contentResolver; mBgHandler = bgHandler; shellInit.addInitCallback(this::onInit, this); + mAnimationBackground = backAnimationBackground; } @VisibleForTesting @@ -228,8 +179,27 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void onInit() { setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler); + createAdapter(); mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION, this::createExternalInterface, this); + + initBackAnimationRunners(); + } + + private void initBackAnimationRunners() { + if (!IS_U_ANIMATION_ENABLED) { + return; + } + + final CrossTaskBackAnimation crossTaskAnimation = + new CrossTaskBackAnimation(mContext, mAnimationBackground); + mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK, + crossTaskAnimation.mBackAnimationRunner); + final CrossActivityAnimation crossActivityAnimation = + new CrossActivityAnimation(mContext, mAnimationBackground); + mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, + crossActivityAnimation.mBackAnimationRunner); + // TODO (236760237): register dialog close animation when it's completed. } private void setupAnimationDeveloperSettingsObserver( @@ -254,8 +224,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF); boolean isEnabled = settingValue == SETTING_VALUE_ON; mEnableAnimations.set(isEnabled); - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", - isEnabled); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled); } public BackAnimation getBackAnimationImpl() { @@ -306,21 +275,19 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } @Override - public void setBackToLauncherCallback(IOnBackInvokedCallback callback) { + public void setBackToLauncherCallback(IOnBackInvokedCallback callback, + IRemoteAnimationRunner runner) { executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback", - (controller) -> controller.setBackToLauncherCallback(callback)); + (controller) -> controller.registerAnimation( + BackNavigationInfo.TYPE_RETURN_TO_HOME, + new BackAnimationRunner(callback, runner))); } @Override public void clearBackToLauncherCallback() { executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback", - (controller) -> controller.clearBackToLauncherCallback()); - } - - @Override - public void onBackToLauncherAnimationFinished() { - executeRemoteCallWithTaskPermission(mController, "onBackToLauncherAnimationFinished", - (controller) -> controller.onBackToLauncherAnimationFinished()); + (controller) -> controller.unregisterAnimation( + BackNavigationInfo.TYPE_RETURN_TO_HOME)); } @Override @@ -329,32 +296,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } - @VisibleForTesting - void setBackToLauncherCallback(IOnBackInvokedCallback callback) { - mBackToLauncherCallback = callback; - if (USE_TRANSITION) { - createAdaptor(); - } + void registerAnimation(@BackNavigationInfo.BackTargetType int type, + @NonNull BackAnimationRunner runner) { + mAnimationDefinition.set(type, runner); } - private void clearBackToLauncherCallback() { - mBackToLauncherCallback = null; - } - - @VisibleForTesting - void onBackToLauncherAnimationFinished() { - final boolean triggerBack = mTriggerBack; - IOnBackInvokedCallback callback = mBackNavigationInfo != null - ? mBackNavigationInfo.getOnBackInvokedCallback() : null; - // Make sure the notification sequence should be controller > client. - finishAnimation(); - if (callback != null) { - if (triggerBack) { - dispatchOnBackInvoked(callback); - } else { - dispatchOnBackCancelled(callback); - } - } + void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) { + mAnimationDefinition.remove(type); } /** @@ -363,7 +311,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ public void onMotionEvent(float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) { - if (mTransitionInProgress) { + if (mPostCommitAnimationInProgress) { return; } @@ -380,7 +328,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont onGestureStarted(touchX, touchY, swipeEdge); mShouldStartOnNextMoveEvent = false; } - onMove(touchX, touchY, swipeEdge); + onMove(); } else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Finishing gesture with event action: %d", keyAction); @@ -395,20 +343,19 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted); if (mBackGestureStarted || mBackNavigationInfo != null) { Log.e(TAG, "Animation is being initialized but is already started."); - finishAnimation(); + finishBackNavigation(); } mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge); mBackGestureStarted = true; try { - boolean requestAnimation = mEnableAnimations.get(); - mBackNavigationInfo = mActivityTaskManager.startBackNavigation(requestAnimation, - mFocusObserver, mBackAnimationAdaptor); + mBackNavigationInfo = mActivityTaskManager.startBackNavigation( + mFocusObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null); onBackNavigationInfoReceived(mBackNavigationInfo); } catch (RemoteException remoteException) { Log.e(TAG, "Failed to initAnimation", remoteException); - finishAnimation(); + finishBackNavigation(); } } @@ -418,82 +365,29 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont Log.e(TAG, "Received BackNavigationInfo is null."); return; } - int backType = backNavigationInfo.getType(); - IOnBackInvokedCallback targetCallback = null; - final boolean dispatchToLauncher = shouldDispatchToLauncher(backType); - if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) { - HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer(); - if (hardwareBuffer != null) { - displayTargetScreenshot(hardwareBuffer, - backNavigationInfo.getTaskWindowConfiguration()); - } - targetCallback = mBackNavigationInfo.getOnBackInvokedCallback(); - mTransaction.apply(); - } else if (dispatchToLauncher) { - targetCallback = mBackToLauncherCallback; - if (USE_TRANSITION) { - mCachingBackDispatcher.startWaitingAnimation(); + final int backType = backNavigationInfo.getType(); + final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(); + if (shouldDispatchToAnimator) { + if (mAnimationDefinition.contains(backType)) { + mActiveCallback = mAnimationDefinition.get(backType).getCallback(); + mAnimationDefinition.get(backType).startGesture(); + } else { + mActiveCallback = null; } - } else if (backType == BackNavigationInfo.TYPE_CALLBACK) { - targetCallback = mBackNavigationInfo.getOnBackInvokedCallback(); - } - if (!USE_TRANSITION || !dispatchToLauncher) { - dispatchOnBackStarted( - targetCallback, - mTouchTracker.createStartEvent( - mBackNavigationInfo.getDepartingAnimationTarget())); + } else { + mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback(); + dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null)); } } - /** - * Display the screenshot of the activity beneath. - * - * @param hardwareBuffer The buffer containing the screenshot. - */ - private void displayTargetScreenshot(@NonNull HardwareBuffer hardwareBuffer, - WindowConfiguration taskWindowConfiguration) { - SurfaceControl screenshotSurface = - mBackNavigationInfo == null ? null : mBackNavigationInfo.getScreenshotSurface(); - if (screenshotSurface == null) { - Log.e(TAG, "BackNavigationInfo doesn't contain a surface for the screenshot. "); + private void onMove() { + if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get() + || mActiveCallback == null) { return; } - // Scale the buffer to fill the whole Task - float sx = 1; - float sy = 1; - float w = taskWindowConfiguration.getBounds().width(); - float h = taskWindowConfiguration.getBounds().height(); - - if (w != hardwareBuffer.getWidth()) { - sx = w / hardwareBuffer.getWidth(); - } - - if (h != hardwareBuffer.getHeight()) { - sy = h / hardwareBuffer.getHeight(); - } - mTransaction.setScale(screenshotSurface, sx, sy); - mTransaction.setBuffer(screenshotSurface, hardwareBuffer); - mTransaction.setVisibility(screenshotSurface, true); - } - - private void onMove(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) { - if (!mBackGestureStarted || mBackNavigationInfo == null) { - return; - } final BackMotionEvent backEvent = mTouchTracker.createProgressEvent(); - if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) { - dispatchOnBackProgressed(mBackToLauncherCallback, backEvent); - } else if (mEnableAnimations.get()) { - int backType = mBackNavigationInfo.getType(); - IOnBackInvokedCallback targetCallback; - if (shouldDispatchToLauncher(backType)) { - targetCallback = mBackToLauncherCallback; - } else { - targetCallback = mBackNavigationInfo.getOnBackInvokedCallback(); - } - dispatchOnBackProgressed(targetCallback, backEvent); - } + dispatchOnBackProgressed(mActiveCallback, backEvent); } private void injectBackKey() { @@ -515,62 +409,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } - private void onGestureFinished(boolean fromTouch) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack); - if (!mBackGestureStarted) { - finishAnimation(); - return; - } - - if (fromTouch) { - // Let touch reset the flag otherwise it will start a new back navigation and refresh - // the info when received a new move event. - mBackGestureStarted = false; - } - - if (mTransitionInProgress) { - return; - } - - if (mBackNavigationInfo == null) { - // No focus window found or core are running recents animation, inject back key as - // legacy behavior. - if (mTriggerBack) { - injectBackKey(); - } - finishAnimation(); - return; - } - - int backType = mBackNavigationInfo.getType(); - boolean shouldDispatchToLauncher = shouldDispatchToLauncher(backType); - IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher - ? mBackToLauncherCallback - : mBackNavigationInfo.getOnBackInvokedCallback(); - if (mCachingBackDispatcher.set(targetCallback, mTriggerBack)) { - return; - } - if (shouldDispatchToLauncher) { - startTransition(); - } - if (mTriggerBack) { - dispatchOnBackInvoked(targetCallback); - } else { - dispatchOnBackCancelled(targetCallback); - } - if (backType != BackNavigationInfo.TYPE_RETURN_TO_HOME || !shouldDispatchToLauncher) { - // Launcher callback missing. Simply finish animation. - finishAnimation(); - } - } - - private boolean shouldDispatchToLauncher(int backType) { - return backType == BackNavigationInfo.TYPE_RETURN_TO_HOME - && mBackToLauncherCallback != null - && mEnableAnimations.get() + private boolean shouldDispatchToAnimator() { + return mEnableAnimations.get() && mBackNavigationInfo != null - && ((USE_TRANSITION && mBackNavigationInfo.isPrepareRemoteAnimation()) - || mBackNavigationInfo.getDepartingAnimationTarget() != null); + && mBackNavigationInfo.isPrepareRemoteAnimation(); } private void dispatchOnBackStarted(IOnBackInvokedCallback callback, @@ -579,7 +421,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return; } try { - if (shouldDispatchAnimation(callback)) { + if (mEnableAnimations.get()) { callback.onBackStarted(backEvent); } } catch (RemoteException e) { @@ -603,7 +445,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return; } try { - if (shouldDispatchAnimation(callback)) { + if (mEnableAnimations.get()) { callback.onBackCancelled(); } } catch (RemoteException e) { @@ -617,7 +459,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return; } try { - if (shouldDispatchAnimation(callback)) { + if (mEnableAnimations.get()) { callback.onBackProgressed(backEvent); } } catch (RemoteException e) { @@ -626,15 +468,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) { - return (IS_U_ANIMATION_ENABLED || callback == mBackToLauncherCallback) - && mEnableAnimations.get(); + // TODO(b/258698745): Only dispatch to animation callbacks. + return mEnableAnimations.get(); } /** * Sets to true when the back gesture has passed the triggering threshold, false otherwise. */ public void setTriggerBack(boolean triggerBack) { - if (mTransitionInProgress) { + if (mPostCommitAnimationInProgress) { return; } mTriggerBack = triggerBack; @@ -643,102 +485,177 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void setSwipeThresholds(float triggerThreshold, float progressThreshold) { mTouchTracker.setProgressThreshold(progressThreshold); - mTriggerThreshold = triggerThreshold; } - private void finishAnimation() { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()"); - mTouchTracker.reset(); - BackNavigationInfo backNavigationInfo = mBackNavigationInfo; - boolean triggerBack = mTriggerBack; - mBackNavigationInfo = null; - mAnimationTarget = null; - mTriggerBack = false; - mShouldStartOnNextMoveEvent = false; - if (backNavigationInfo == null) { - return; + private void invokeOrCancelBack() { + // Make a synchronized call to core before dispatch back event to client side. + // If the close transition happens before the core receives onAnimationFinished, there will + // play a second close animation for that transition. + if (mBackAnimationFinishedCallback != null) { + try { + mBackAnimationFinishedCallback.onAnimationFinished(mTriggerBack); + } catch (RemoteException e) { + Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e); + } + mBackAnimationFinishedCallback = null; } - if (!USE_TRANSITION) { - RemoteAnimationTarget animationTarget = backNavigationInfo - .getDepartingAnimationTarget(); - if (animationTarget != null) { - if (animationTarget.leash != null && animationTarget.leash.isValid()) { - mTransaction.remove(animationTarget.leash); - } - } - SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface(); - if (screenshotSurface != null && screenshotSurface.isValid()) { - mTransaction.remove(screenshotSurface); + if (mBackNavigationInfo != null) { + final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); + if (mTriggerBack) { + dispatchOnBackInvoked(callback); + } else { + dispatchOnBackCancelled(callback); } - mTransaction.apply(); - } - stopTransition(); - backNavigationInfo.onBackNavigationFinished(triggerBack); - if (USE_TRANSITION) { - final IBackNaviAnimationController controller = mBackAnimationController; - if (controller != null) { - try { - controller.finish(triggerBack); - } catch (RemoteException r) { - // Oh no! - } + } + finishBackNavigation(); + } + + /** + * Called when the gesture is released, then it could start the post commit animation. + */ + private void onGestureFinished(boolean fromTouch) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack); + if (!mBackGestureStarted) { + finishBackNavigation(); + return; + } + + if (fromTouch) { + // Let touch reset the flag otherwise it will start a new back navigation and refresh + // the info when received a new move event. + mBackGestureStarted = false; + } + + if (mPostCommitAnimationInProgress) { + ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running"); + return; + } + + if (mBackNavigationInfo == null) { + // No focus window found or core are running recents animation, inject back key as + // legacy behavior. + if (mTriggerBack) { + injectBackKey(); } - mBackAnimationController = null; + finishBackNavigation(); + return; + } + + final int backType = mBackNavigationInfo.getType(); + // Simply trigger and finish back navigation when no animator defined. + if (!shouldDispatchToAnimator() || mActiveCallback == null) { + invokeOrCancelBack(); + return; + } + + final BackAnimationRunner runner = mAnimationDefinition.get(backType); + if (runner.isWaitingAnimation()) { + ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready."); + return; } + startPostCommitAnimation(); } - private void startTransition() { - if (mTransitionInProgress) { + /** + * Start the phase 2 animation when gesture is released. + * Callback to {@link #onBackAnimationFinished} when it is finished or timeout. + */ + private void startPostCommitAnimation() { + if (mPostCommitAnimationInProgress) { return; } - mTransitionInProgress = true; - mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()"); + mPostCommitAnimationInProgress = true; + mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); + + // The next callback should be {@link #onBackAnimationFinished}. + if (mTriggerBack) { + dispatchOnBackInvoked(mActiveCallback); + } else { + dispatchOnBackCancelled(mActiveCallback); + } } - private void stopTransition() { - if (!mTransitionInProgress) { + /** + * Called when the post commit animation is completed or timeout. + * This will trigger the real {@link IOnBackInvokedCallback} behavior. + */ + @VisibleForTesting + void onBackAnimationFinished() { + if (!mPostCommitAnimationInProgress) { return; } - mShellExecutor.removeCallbacks(mResetTransitionRunnable); - mTransitionInProgress = false; + // Stop timeout runner. + mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); + mPostCommitAnimationInProgress = false; + + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()"); + + // Trigger the real back. + invokeOrCancelBack(); } - private void createAdaptor() { - mIBackAnimationRunner = new IBackAnimationRunner.Stub() { + /** + * This should be called after the whole back navigation is completed. + */ + @VisibleForTesting + void finishBackNavigation() { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()"); + mShouldStartOnNextMoveEvent = false; + mTouchTracker.reset(); + mActiveCallback = null; + if (mBackNavigationInfo != null) { + mBackNavigationInfo.onBackNavigationFinished(mTriggerBack); + mBackNavigationInfo = null; + } + mTriggerBack = false; + } + + private void createAdapter() { + IBackAnimationRunner runner = new IBackAnimationRunner.Stub() { @Override - public void onAnimationCancelled() { - // no op for now - } - @Override // Binder interface - public void onAnimationStart(IBackNaviAnimationController controller, int type, - RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps) { + public void onAnimationStart(int type, RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, + IBackAnimationFinishedCallback finishedCallback) { mShellExecutor.execute(() -> { - mBackAnimationController = controller; - for (int i = 0; i < apps.length; i++) { - final RemoteAnimationTarget target = apps[i]; - if (MODE_CLOSING == target.mode) { - mAnimationTarget = target; - } else if (MODE_OPENING == target.mode) { - // TODO Home activity should handle the visibility for itself - // once it finish relayout for orientation change - SurfaceControl.Transaction tx = - new SurfaceControl.Transaction(); - tx.setAlpha(target.leash, 1); - tx.apply(); + final BackAnimationRunner runner = mAnimationDefinition.get(type); + if (runner == null) { + Log.e(TAG, "Animation didn't be defined for type " + + BackNavigationInfo.typeToString(type)); + if (finishedCallback != null) { + try { + finishedCallback.onAnimationFinished(false); + } catch (RemoteException e) { + Log.w(TAG, "Failed call IBackNaviAnimationController", e); + } } + return; } - dispatchOnBackStarted(mBackToLauncherCallback, - mTouchTracker.createStartEvent(mAnimationTarget)); - final BackMotionEvent backInit = mTouchTracker.createProgressEvent(); - if (!mCachingBackDispatcher.consume()) { - dispatchOnBackProgressed(mBackToLauncherCallback, backInit); + mBackAnimationFinishedCallback = finishedCallback; + + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()"); + runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute( + BackAnimationController.this::onBackAnimationFinished)); + + if (apps.length >= 1) { + dispatchOnBackStarted( + mActiveCallback, mTouchTracker.createStartEvent(apps[0])); + } + + if (!mBackGestureStarted) { + // if the down -> up gesture happened before animation start, we have to + // trigger the uninterruptible transition to finish the back animation. + final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(); + dispatchOnBackProgressed(mActiveCallback, backFinish); + startPostCommitAnimation(); } }); } + + @Override + public void onAnimationCancelled() { } }; - mBackAnimationAdaptor = new BackAnimationAdaptor(mIBackAnimationRunner, - BackNavigationInfo.TYPE_RETURN_TO_HOME); + mBackAnimationAdapter = new BackAnimationAdapter(runner); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java new file mode 100644 index 000000000000..d70b8f53a911 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 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.back; + +import static android.view.WindowManager.TRANSIT_OLD_UNSET; + +import android.annotation.NonNull; +import android.os.RemoteException; +import android.util.Log; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationTarget; +import android.window.IBackAnimationRunner; +import android.window.IOnBackInvokedCallback; + +/** + * Used to register the animation callback and runner, it will trigger result if gesture was finish + * before it received IBackAnimationRunner#onAnimationStart, so the controller could continue + * trigger the real back behavior. + */ +class BackAnimationRunner { + private static final String TAG = "ShellBackPreview"; + + private final IOnBackInvokedCallback mCallback; + private final IRemoteAnimationRunner mRunner; + + // Whether we are waiting to receive onAnimationStart + private boolean mWaitingAnimation; + + BackAnimationRunner(@NonNull IOnBackInvokedCallback callback, + @NonNull IRemoteAnimationRunner runner) { + mCallback = callback; + mRunner = runner; + } + + /** Returns the registered animation runner */ + IRemoteAnimationRunner getRunner() { + return mRunner; + } + + /** Returns the registered animation callback */ + IOnBackInvokedCallback getCallback() { + return mCallback; + } + + /** + * Called from {@link IBackAnimationRunner}, it will deliver these + * {@link RemoteAnimationTarget}s to the corresponding runner. + */ + void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, Runnable finishedCallback) { + final IRemoteAnimationFinishedCallback callback = + new IRemoteAnimationFinishedCallback.Stub() { + @Override + public void onAnimationFinished() { + finishedCallback.run(); + } + }; + mWaitingAnimation = false; + try { + mRunner.onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers, + nonApps, callback); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onAnimationStart", e); + } + } + + void startGesture() { + mWaitingAnimation = true; + } + + boolean isWaitingAnimation() { + return mWaitingAnimation; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java new file mode 100644 index 000000000000..5d384944821c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2022 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.back; + +import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import static android.view.RemoteAnimationTarget.MODE_OPENING; + +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.RemoteException; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.window.BackEvent; +import android.window.BackMotionEvent; +import android.window.BackProgressAnimator; +import android.window.IOnBackInvokedCallback; + +import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.common.annotations.ShellMainThread; + +/** Class that defines cross-activity animation. */ +@ShellMainThread +class CrossActivityAnimation { + /** + * Minimum scale of the entering/closing window. + */ + private static final float MIN_WINDOW_SCALE = 0.9f; + + /** + * Minimum alpha of the closing/entering window. + */ + private static final float CLOSING_MIN_WINDOW_ALPHA = 0.5f; + + /** + * Progress value to fly out closing window and fly in entering window. + */ + private static final float SWITCH_ENTERING_WINDOW_PROGRESS = 0.5f; + + /** Max window translation in the Y axis. */ + private static final int WINDOW_MAX_DELTA_Y = 160; + + /** Duration of fade in/out entering window. */ + private static final int FADE_IN_DURATION = 100; + /** Duration of post animation after gesture committed. */ + private static final int POST_ANIMATION_DURATION = 350; + private static final Interpolator INTERPOLATOR = Interpolators.EMPHASIZED; + + private final Rect mStartTaskRect = new Rect(); + private final float mCornerRadius; + + // The closing window properties. + private final RectF mClosingRect = new RectF(); + + // The entering window properties. + private final Rect mEnteringStartRect = new Rect(); + private final RectF mEnteringRect = new RectF(); + + private float mCurrentAlpha = 1.0f; + + private float mEnteringMargin = 0; + private ValueAnimator mEnteringAnimator; + private boolean mEnteringWindowShow = false; + + private final PointF mInitialTouchPos = new PointF(); + + private final Matrix mTransformMatrix = new Matrix(); + + private final float[] mTmpFloat9 = new float[9]; + + private RemoteAnimationTarget mEnteringTarget; + private RemoteAnimationTarget mClosingTarget; + private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + + private boolean mBackInProgress = false; + + private PointF mTouchPos = new PointF(); + private IRemoteAnimationFinishedCallback mFinishCallback; + + private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); + final BackAnimationRunner mBackAnimationRunner; + + private final BackAnimationBackground mBackground; + + CrossActivityAnimation(Context context, BackAnimationBackground background) { + mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); + mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner()); + mBackground = background; + } + + private static float mapRange(float value, float min, float max) { + return min + (value * (max - min)); + } + + private float getInterpolatedProgress(float backProgress) { + return INTERPOLATOR.getInterpolation(backProgress); + } + + private void startBackAnimation() { + if (mEnteringTarget == null || mClosingTarget == null) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null."); + return; + } + mTransaction.setAnimationTransaction(); + + // Offset start rectangle to align task bounds. + mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds()); + mStartTaskRect.offsetTo(0, 0); + + // Draw background with task background color. + mBackground.ensureBackground( + mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction); + } + + private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) { + final float scale = targetRect.width() / mStartTaskRect.width(); + mTransformMatrix.reset(); + mTransformMatrix.setScale(scale, scale); + mTransformMatrix.postTranslate(targetRect.left, targetRect.top); + mTransaction.setAlpha(leash, targetAlpha) + .setMatrix(leash, mTransformMatrix, mTmpFloat9) + .setWindowCrop(leash, mStartTaskRect) + .setCornerRadius(leash, mCornerRadius); + } + + private void finishAnimation() { + if (mEnteringTarget != null) { + mEnteringTarget.leash.release(); + mEnteringTarget = null; + } + if (mClosingTarget != null) { + mClosingTarget.leash.release(); + mClosingTarget = null; + } + if (mBackground != null) { + mBackground.removeBackground(mTransaction); + } + + mTransaction.apply(); + mBackInProgress = false; + mTransformMatrix.reset(); + mInitialTouchPos.set(0, 0); + mEnteringWindowShow = false; + mEnteringMargin = 0; + mEnteringAnimator = null; + + if (mFinishCallback != null) { + try { + mFinishCallback.onAnimationFinished(); + } catch (RemoteException e) { + e.printStackTrace(); + } + mFinishCallback = null; + } + } + + private void onGestureProgress(@NonNull BackEvent backEvent) { + if (!mBackInProgress) { + mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); + mBackInProgress = true; + } + mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); + + if (mEnteringTarget == null || mClosingTarget == null) { + return; + } + + final float progress = getInterpolatedProgress(backEvent.getProgress()); + final float touchY = mTouchPos.y; + + final int width = mStartTaskRect.width(); + final int height = mStartTaskRect.height(); + + final float closingScale = mapRange(progress, 1, MIN_WINDOW_SCALE); + + final float closingWidth = closingScale * width; + final float closingHeight = (float) height / width * closingWidth; + + // Move the window along the X axis. + final float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2; + + // Move the window along the Y axis. + final float deltaYRatio = (touchY - mInitialTouchPos.y) / height; + final float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y; + final float closingTop = (height - closingHeight) * 0.5f + deltaY; + mClosingRect.set( + closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight); + mEnteringRect.set(mClosingRect); + + // Switch closing/entering targets while reach to the threshold progress. + if (showEnteringWindow(progress > SWITCH_ENTERING_WINDOW_PROGRESS)) { + return; + } + + // Present windows and update the alpha. + mCurrentAlpha = Math.max(mapRange(progress, 1.0f, 0), CLOSING_MIN_WINDOW_ALPHA); + mClosingRect.offset(mEnteringMargin, 0); + mEnteringRect.offset(mEnteringMargin - width, 0); + + applyTransform( + mClosingTarget.leash, mClosingRect, mEnteringWindowShow ? 0.01f : mCurrentAlpha); + applyTransform( + mEnteringTarget.leash, mEnteringRect, mEnteringWindowShow ? mCurrentAlpha : 0.01f); + mTransaction.apply(); + } + + private boolean showEnteringWindow(boolean show) { + if (mEnteringAnimator == null) { + mEnteringAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(FADE_IN_DURATION); + mEnteringAnimator.setInterpolator(new AccelerateInterpolator()); + mEnteringAnimator.addUpdateListener(animation -> { + float progress = animation.getAnimatedFraction(); + final int width = mStartTaskRect.width(); + mEnteringMargin = width * progress; + // We don't animate to 0 or the surface would become invisible and lose focus. + final float alpha = progress >= 0.5f ? 0.01f + : mapRange(progress * 2, mCurrentAlpha, 0.01f); + mClosingRect.offset(mEnteringMargin, 0); + mEnteringRect.offset(mEnteringMargin - width, 0); + + applyTransform(mClosingTarget.leash, mClosingRect, alpha); + applyTransform(mEnteringTarget.leash, mEnteringRect, mCurrentAlpha); + mTransaction.apply(); + }); + } + + if (mEnteringAnimator.isRunning()) { + return true; + } + + if (mEnteringWindowShow == show) { + return false; + } + + mEnteringWindowShow = show; + if (show) { + mEnteringAnimator.start(); + } else { + mEnteringAnimator.reverse(); + } + return true; + } + + private void onGestureCommitted() { + if (mEnteringTarget == null || mClosingTarget == null) { + finishAnimation(); + return; + } + + // End the fade in animation. + if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) { + mEnteringAnimator.cancel(); + } + + // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current + // coordinate of the gesture driven phase. + mEnteringRect.round(mEnteringStartRect); + mTransaction.hide(mClosingTarget.leash); + + ValueAnimator valueAnimator = + ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION); + valueAnimator.setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(animation -> { + float progress = animation.getAnimatedFraction(); + updatePostCommitEnteringAnimation(progress); + mTransaction.apply(); + }); + + valueAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finishAnimation(); + } + }); + valueAnimator.start(); + } + + private void updatePostCommitEnteringAnimation(float progress) { + float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left); + float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top); + float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width()); + float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height()); + float alpha = mapRange(progress, mCurrentAlpha, 1.0f); + + mEnteringRect.set(left, top, left + width, top + height); + applyTransform(mEnteringTarget.leash, mEnteringRect, alpha); + } + + private final class Callback extends IOnBackInvokedCallback.Default { + @Override + public void onBackStarted(BackMotionEvent backEvent) { + mProgressAnimator.onBackStarted(backEvent, + CrossActivityAnimation.this::onGestureProgress); + } + + @Override + public void onBackProgressed(@NonNull BackMotionEvent backEvent) { + mProgressAnimator.onBackProgressed(backEvent); + } + + @Override + public void onBackCancelled() { + // End the fade in animation. + if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) { + mEnteringAnimator.cancel(); + } + mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation); + } + + @Override + public void onBackInvoked() { + mProgressAnimator.reset(); + onGestureCommitted(); + } + } + + private final class Runner extends IRemoteAnimationRunner.Default { + @Override + public void onAnimationStart( + int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to activity animation."); + for (RemoteAnimationTarget a : apps) { + if (a.mode == MODE_CLOSING) { + mClosingTarget = a; + } + if (a.mode == MODE_OPENING) { + mEnteringTarget = a; + } + } + + startBackAnimation(); + mFinishCallback = finishedCallback; + } + + @Override + public void onAnimationCancelled(boolean isKeyguardOccluded) { + finishAnimation(); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java new file mode 100644 index 000000000000..99a434aff799 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2022 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.back; + +import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import static android.view.RemoteAnimationTarget.MODE_OPENING; +import static android.window.BackEvent.EDGE_RIGHT; + +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.RemoteException; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Interpolator; +import android.window.BackEvent; +import android.window.BackMotionEvent; +import android.window.BackProgressAnimator; +import android.window.IOnBackInvokedCallback; + +import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.annotations.ShellMainThread; + +/** + * Controls the animation of swiping back and returning to another task. + * + * This is a two part animation. The first part is an animation that tracks gesture location to + * scale and move the closing and entering app windows. + * Once the gesture is committed, the second part remains the closing window in place. + * The entering window plays the rest of app opening transition to enter full screen. + * + * This animation is used only for apps that enable back dispatching via + * {@link android.window.OnBackInvokedDispatcher}. The controller registers + * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back + * navigation to launcher starts. + */ +@ShellMainThread +class CrossTaskBackAnimation { + private static final int BACKGROUNDCOLOR = 0x43433A; + + /** + * Minimum scale of the entering window. + */ + private static final float ENTERING_MIN_WINDOW_SCALE = 0.85f; + + /** + * Minimum scale of the closing window. + */ + private static final float CLOSING_MIN_WINDOW_SCALE = 0.75f; + + /** + * Minimum color scale of the closing window. + */ + private static final float CLOSING_MIN_WINDOW_COLOR_SCALE = 0.1f; + + /** + * The margin between the entering window and the closing window + */ + private static final int WINDOW_MARGIN = 35; + + /** Max window translation in the Y axis. */ + private static final int WINDOW_MAX_DELTA_Y = 160; + + private final Rect mStartTaskRect = new Rect(); + private final float mCornerRadius; + + // The closing window properties. + private final RectF mClosingCurrentRect = new RectF(); + + // The entering window properties. + private final Rect mEnteringStartRect = new Rect(); + private final RectF mEnteringCurrentRect = new RectF(); + + private final PointF mInitialTouchPos = new PointF(); + private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator(); + + private final Matrix mTransformMatrix = new Matrix(); + + private final float[] mTmpFloat9 = new float[9]; + private final float[] mTmpTranslate = {0, 0, 0}; + + private RemoteAnimationTarget mEnteringTarget; + private RemoteAnimationTarget mClosingTarget; + private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + + private boolean mBackInProgress = false; + + private boolean mIsRightEdge; + private float mProgress = 0; + private PointF mTouchPos = new PointF(); + private IRemoteAnimationFinishedCallback mFinishCallback; + private BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); + final BackAnimationRunner mBackAnimationRunner; + + private final BackAnimationBackground mBackground; + + CrossTaskBackAnimation(Context context, BackAnimationBackground background) { + mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); + mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner()); + mBackground = background; + } + + private float getInterpolatedProgress(float backProgress) { + return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress); + } + + private void startBackAnimation() { + if (mEnteringTarget == null || mClosingTarget == null) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null."); + return; + } + + // Offset start rectangle to align task bounds. + mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds()); + mStartTaskRect.offsetTo(0, 0); + + // Draw background. + mBackground.ensureBackground(BACKGROUNDCOLOR, mTransaction); + } + + private void updateGestureBackProgress(float progress, BackEvent event) { + if (mEnteringTarget == null || mClosingTarget == null) { + return; + } + + float touchX = event.getTouchX(); + float touchY = event.getTouchY(); + float dX = Math.abs(touchX - mInitialTouchPos.x); + + // The 'follow width' is the width of the window if it completely matches + // the gesture displacement. + final int width = mStartTaskRect.width(); + final int height = mStartTaskRect.height(); + + // The 'progress width' is the width of the window if it strictly linearly interpolates + // to minimum scale base on progress. + float enteringScale = mapRange(progress, 1, ENTERING_MIN_WINDOW_SCALE); + float closingScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_SCALE); + float closingColorScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_COLOR_SCALE); + + // The final width is derived from interpolating between the follow with and progress width + // using gesture progress. + float enteringWidth = enteringScale * width; + float closingWidth = closingScale * width; + float enteringHeight = (float) height / width * enteringWidth; + float closingHeight = (float) height / width * closingWidth; + + float deltaYRatio = (touchY - mInitialTouchPos.y) / height; + // Base the window movement in the Y axis on the touch movement in the Y axis. + float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y; + // Move the window along the Y axis. + float closingTop = (height - closingHeight) * 0.5f + deltaY; + float enteringTop = (height - enteringHeight) * 0.5f + deltaY; + // Move the window along the X axis. + float right = width - (progress * WINDOW_MARGIN); + float left = right - closingWidth; + + mClosingCurrentRect.set(left, closingTop, right, closingTop + closingHeight); + mEnteringCurrentRect.set(left - enteringWidth - WINDOW_MARGIN, enteringTop, + left - WINDOW_MARGIN, enteringTop + enteringHeight); + + applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius); + applyColorTransform(mClosingTarget.leash, closingColorScale); + applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius); + mTransaction.apply(); + } + + private void updatePostCommitClosingAnimation(float progress) { + mTransaction.setLayer(mClosingTarget.leash, 0); + float alpha = mapRange(progress, 1, 0); + mTransaction.setAlpha(mClosingTarget.leash, alpha); + } + + private void updatePostCommitEnteringAnimation(float progress) { + float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left); + float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top); + float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width()); + float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height()); + + mEnteringCurrentRect.set(left, top, left + width, top + height); + applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius); + } + + /** Transform the target window to match the target rect. */ + private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) { + if (leash == null) { + return; + } + + final float scale = targetRect.width() / mStartTaskRect.width(); + mTransformMatrix.reset(); + mTransformMatrix.setScale(scale, scale); + mTransformMatrix.postTranslate(targetRect.left, targetRect.top); + mTransaction.setMatrix(leash, mTransformMatrix, mTmpFloat9) + .setWindowCrop(leash, mStartTaskRect) + .setCornerRadius(leash, cornerRadius); + } + + private void applyColorTransform(SurfaceControl leash, float colorScale) { + if (leash == null) { + return; + } + computeScaleTransformMatrix(colorScale, mTmpFloat9); + mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate); + } + + static void computeScaleTransformMatrix(float scale, float[] matrix) { + matrix[0] = scale; + matrix[1] = 0; + matrix[2] = 0; + matrix[3] = 0; + matrix[4] = scale; + matrix[5] = 0; + matrix[6] = 0; + matrix[7] = 0; + matrix[8] = scale; + } + + private void finishAnimation() { + if (mEnteringTarget != null) { + mEnteringTarget.leash.release(); + mEnteringTarget = null; + } + if (mClosingTarget != null) { + mClosingTarget.leash.release(); + mClosingTarget = null; + } + + if (mBackground != null) { + mBackground.removeBackground(mTransaction); + } + + mTransaction.apply(); + mBackInProgress = false; + mTransformMatrix.reset(); + mClosingCurrentRect.setEmpty(); + mInitialTouchPos.set(0, 0); + + if (mFinishCallback != null) { + try { + mFinishCallback.onAnimationFinished(); + } catch (RemoteException e) { + e.printStackTrace(); + } + mFinishCallback = null; + } + } + + private void onGestureProgress(@NonNull BackEvent backEvent) { + if (!mBackInProgress) { + mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); + mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT; + mBackInProgress = true; + } + mProgress = backEvent.getProgress(); + mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); + updateGestureBackProgress(getInterpolatedProgress(mProgress), backEvent); + } + + private void onGestureCommitted() { + if (mEnteringTarget == null || mClosingTarget == null) { + finishAnimation(); + return; + } + + // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current + // coordinate of the gesture driven phase. + mEnteringCurrentRect.round(mEnteringStartRect); + + ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(300); + valueAnimator.setInterpolator(mInterpolator); + valueAnimator.addUpdateListener(animation -> { + float progress = animation.getAnimatedFraction(); + updatePostCommitEnteringAnimation(progress); + updatePostCommitClosingAnimation(progress); + mTransaction.apply(); + }); + + valueAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finishAnimation(); + } + }); + valueAnimator.start(); + } + + private static float mapRange(float value, float min, float max) { + return min + (value * (max - min)); + } + + private final class Callback extends IOnBackInvokedCallback.Default { + @Override + public void onBackStarted(BackMotionEvent backEvent) { + mProgressAnimator.onBackStarted(backEvent, + CrossTaskBackAnimation.this::onGestureProgress); + } + + @Override + public void onBackProgressed(@NonNull BackMotionEvent backEvent) { + mProgressAnimator.onBackProgressed(backEvent); + } + + @Override + public void onBackCancelled() { + mProgressAnimator.onBackCancelled(CrossTaskBackAnimation.this::finishAnimation); + } + + @Override + public void onBackInvoked() { + mProgressAnimator.reset(); + onGestureCommitted(); + } + }; + + private final class Runner extends IRemoteAnimationRunner.Default { + @Override + public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation."); + for (RemoteAnimationTarget a : apps) { + if (a.mode == MODE_CLOSING) { + mClosingTarget = a; + } + if (a.mode == MODE_OPENING) { + mEnteringTarget = a; + } + } + + startBackAnimation(); + mFinishCallback = finishedCallback; + } + }; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl index 6311f879fd45..2b2a0e397792 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl @@ -17,29 +17,21 @@ package com.android.wm.shell.back; import android.window.IOnBackInvokedCallback; +import android.view.IRemoteAnimationRunner; /** * Interface for Launcher process to register back invocation callbacks. */ interface IBackAnimation { - /** - * Sets a {@link IOnBackInvokedCallback} to be invoked when + * Sets a {@link IOnBackInvokedCallback} and a {@link IRemoteAnimationRunner} to be invoked when * back navigation has type {@link BackNavigationInfo#TYPE_RETURN_TO_HOME}. */ - void setBackToLauncherCallback(in IOnBackInvokedCallback callback); + void setBackToLauncherCallback(in IOnBackInvokedCallback callback, + in IRemoteAnimationRunner runner); /** * Clears the previously registered {@link IOnBackInvokedCallback}. */ void clearBackToLauncherCallback(); - - /** - * Notifies Shell that the back to launcher animation has fully finished - * (including the transition animation that runs after the finger is lifted). - * - * At this point the top window leash (if one was created) should be ready to be released. - * //TODO: Remove once we play the transition animation through shell transitions. - */ - void onBackToLauncherAnimationFinished(); } 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 71e15c12b9c0..360bfe78bf07 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 @@ -685,10 +685,18 @@ public class BubbleController implements ConfigurationChangeListener { return; } + mAddedToWindowManager = false; + // Put on background for this binder call, was causing jank + mBackgroundExecutor.execute(() -> { + try { + mContext.unregisterReceiver(mBroadcastReceiver); + } catch (IllegalArgumentException e) { + // Not sure if this happens in production, but was happening in tests + // (b/253647225) + e.printStackTrace(); + } + }); try { - mAddedToWindowManager = false; - // Put on background for this binder call, was causing jank - mBackgroundExecutor.execute(() -> mContext.unregisterReceiver(mBroadcastReceiver)); if (mStackView != null) { mWindowManager.removeView(mStackView); mBubbleData.getOverflow().cleanUpExpandedState(); @@ -706,7 +714,7 @@ public class BubbleController implements ConfigurationChangeListener { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); filter.addAction(Intent.ACTION_SCREEN_OFF); - mContext.registerReceiver(mBroadcastReceiver, filter); + mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 5ea370b65407..8121b206c93a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -53,13 +53,13 @@ import android.util.IntProperty; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; -import android.view.SurfaceControl; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.LinearLayout; +import android.window.ScreenCapture; import androidx.annotation.Nullable; @@ -516,7 +516,7 @@ public class BubbleExpandedView extends LinearLayout { /** Return a GraphicBuffer with the contents of the task view surface. */ @Nullable - SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() { + ScreenCapture.ScreenshotHardwareBuffer snapshotActivitySurface() { if (mIsOverflow) { // For now, just snapshot the view and return it as a hw buffer so that the animation // code for both the tasks and overflow can be the same @@ -525,7 +525,7 @@ public class BubbleExpandedView extends LinearLayout { p.beginRecording(mOverflowView.getWidth(), mOverflowView.getHeight())); p.endRecording(); Bitmap snapshot = Bitmap.createBitmap(p); - return new SurfaceControl.ScreenshotHardwareBuffer( + return new ScreenCapture.ScreenshotHardwareBuffer( snapshot.getHardwareBuffer(), snapshot.getColorSpace(), false /* containsSecureLayers */, @@ -534,7 +534,7 @@ public class BubbleExpandedView extends LinearLayout { if (mTaskView == null || mTaskView.getSurfaceControl() == null) { return null; } - return SurfaceControl.captureLayers( + return ScreenCapture.captureLayers( mTaskView.getSurfaceControl(), new Rect(0, 0, mTaskView.getWidth(), mTaskView.getHeight()), 1 /* scale */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index f2afefe243bc..abe42eec7061 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -51,7 +51,6 @@ import android.util.Log; import android.view.Choreographer; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; @@ -64,6 +63,7 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; +import android.window.ScreenCapture; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -238,7 +238,7 @@ public class BubbleStackView extends FrameLayout * Buffer containing a screenshot of the animating-out bubble. This is drawn into the * SurfaceView during animations. */ - private SurfaceControl.ScreenshotHardwareBuffer mAnimatingOutBubbleBuffer; + private ScreenCapture.ScreenshotHardwareBuffer mAnimatingOutBubbleBuffer; private BubbleFlyoutView mFlyout; /** Runnable that fades out the flyout and then sets it to GONE. */ @@ -920,7 +920,6 @@ public class BubbleStackView extends FrameLayout addView(mAnimatingOutSurfaceContainer); mAnimatingOutSurfaceView = new SurfaceView(getContext()); - mAnimatingOutSurfaceView.setUseAlpha(); mAnimatingOutSurfaceView.setZOrderOnTop(true); boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( mContext.getResources()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 7f7af935ff2b..d0aef2023048 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -16,17 +16,23 @@ package com.android.wm.shell.common; +import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_CANCEL; +import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END; +import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START; +import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY; +import static android.view.inputmethod.ImeTracker.TOKEN_NONE; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.annotation.Nullable; import android.content.ComponentName; -import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.os.RemoteException; -import android.os.ServiceManager; +import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; @@ -34,19 +40,21 @@ import android.view.IWindowManager; import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; -import android.view.InsetsVisibilities; import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowInsets; +import android.view.WindowInsets.Type.InsetsType; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.view.inputmethod.ImeTracker; +import android.view.inputmethod.InputMethodManagerGlobal; import androidx.annotation.VisibleForTesting; -import com.android.internal.view.IInputMethodManager; import com.android.wm.shell.sysui.ShellInit; import java.util.ArrayList; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -114,7 +122,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } if (mDisplayController.getDisplayLayout(displayId).rotation() != pd.mRotation && isImeShowing(displayId)) { - pd.startAnimation(true, false /* forceRestart */); + pd.startAnimation(true, false /* forceRestart */, null /* statsToken */); } } @@ -209,7 +217,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener { final int mDisplayId; final InsetsState mInsetsState = new InsetsState(); - final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); + @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); InsetsSourceControl mImeSourceControl = null; int mAnimationDirection = DIRECTION_NONE; ValueAnimator mAnimation = null; @@ -246,7 +254,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mInsetsState.set(insetsState, true /* copySources */); if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) { if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation"); - startAnimation(mImeShowing, true /* forceRestart */); + startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */); } } @@ -261,7 +269,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged if (activeControl == null) { continue; } - if (activeControl.getType() == InsetsState.ITYPE_IME) { + if (activeControl.getType() == WindowInsets.Type.ime()) { imeSourceControl = activeControl; } } @@ -280,7 +288,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged final boolean positionChanged = !imeSourceControl.getSurfacePosition().equals(lastSurfacePosition); if (positionChanged) { - startAnimation(mImeShowing, true /* forceRestart */); + startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */); } } else { if (!haveSameLeash(mImeSourceControl, imeSourceControl)) { @@ -315,26 +323,27 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } @Override - public void showInsets(int types, boolean fromIme) { + public void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { if ((types & WindowInsets.Type.ime()) == 0) { return; } if (DEBUG) Slog.d(TAG, "Got showInsets for ime"); - startAnimation(true /* show */, false /* forceRestart */); + startAnimation(true /* show */, false /* forceRestart */, statsToken); } @Override - public void hideInsets(int types, boolean fromIme) { + public void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { if ((types & WindowInsets.Type.ime()) == 0) { return; } if (DEBUG) Slog.d(TAG, "Got hideInsets for ime"); - startAnimation(false /* show */, false /* forceRestart */); + startAnimation(false /* show */, false /* forceRestart */, statsToken); } @Override - public void topFocusedWindowChanged(ComponentName component, - InsetsVisibilities requestedVisibilities) { + public void topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes) { // Do nothing } @@ -343,10 +352,12 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged */ private void setVisibleDirectly(boolean visible) { mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible); - mRequestedVisibilities.setVisibility(InsetsState.ITYPE_IME, visible); + mRequestedVisibleTypes = visible + ? mRequestedVisibleTypes | WindowInsets.Type.ime() + : mRequestedVisibleTypes & ~WindowInsets.Type.ime(); try { - mWmService.updateDisplayWindowRequestedVisibilities(mDisplayId, - mRequestedVisibilities); + mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId, + mRequestedVisibleTypes); } catch (RemoteException e) { } } @@ -369,9 +380,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged .navBarFrameHeight(); } - private void startAnimation(final boolean show, final boolean forceRestart) { + private void startAnimation(final boolean show, final boolean forceRestart, + @Nullable ImeTracker.Token statsToken) { final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME); if (imeSource == null || mImeSourceControl == null) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); return; } final Rect newFrame = imeSource.getFrame(); @@ -392,8 +405,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged + (mAnimationDirection == DIRECTION_SHOW ? "SHOW" : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE"))); } - if (!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show) + if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show)) || (mAnimationDirection == DIRECTION_HIDE && !show)) { + ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); return; } boolean seek = false; @@ -437,8 +451,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mTransactionPool.release(t); }); mAnimation.setInterpolator(INTERPOLATOR); + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); mAnimation.addListener(new AnimatorListenerAdapter() { private boolean mCancelled = false; + @Nullable + private final ImeTracker.Token mStatsToken = statsToken; @Override public void onAnimationStart(Animator animation) { @@ -457,8 +474,19 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged : 1.f; t.setAlpha(mImeSourceControl.getLeash(), alpha); if (mAnimationDirection == DIRECTION_SHOW) { + ImeTracker.get().onProgress(mStatsToken, + ImeTracker.PHASE_WM_ANIMATION_RUNNING); t.show(mImeSourceControl.getLeash()); } + if (DEBUG_IME_VISIBILITY) { + EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START, + statsToken != null ? statsToken.getTag() : TOKEN_NONE, + mDisplayId, mAnimationDirection, alpha, startY , endY, + Objects.toString(mImeSourceControl.getLeash()), + Objects.toString(mImeSourceControl.getInsetsHint()), + Objects.toString(mImeSourceControl.getSurfacePosition()), + Objects.toString(mImeFrame)); + } t.apply(); mTransactionPool.release(t); } @@ -466,6 +494,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged @Override public void onAnimationCancel(Animator animation) { mCancelled = true; + if (DEBUG_IME_VISIBILITY) { + EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL, + statsToken != null ? statsToken.getTag() : TOKEN_NONE, mDisplayId, + Objects.toString(mImeSourceControl.getInsetsHint())); + } } @Override @@ -478,8 +511,25 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } dispatchEndPositioning(mDisplayId, mCancelled, t); if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { + ImeTracker.get().onProgress(mStatsToken, + ImeTracker.PHASE_WM_ANIMATION_RUNNING); t.hide(mImeSourceControl.getLeash()); removeImeSurface(); + ImeTracker.get().onHidden(mStatsToken); + } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) { + ImeTracker.get().onShown(mStatsToken); + } else if (mCancelled) { + ImeTracker.get().onCancelled(mStatsToken, + ImeTracker.PHASE_WM_ANIMATION_RUNNING); + } + if (DEBUG_IME_VISIBILITY) { + EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END, + statsToken != null ? statsToken.getTag() : TOKEN_NONE, + mDisplayId, mAnimationDirection, endY, + Objects.toString(mImeSourceControl.getLeash()), + Objects.toString(mImeSourceControl.getInsetsHint()), + Objects.toString(mImeSourceControl.getSurfacePosition()), + Objects.toString(mImeFrame)); } t.apply(); mTransactionPool.release(t); @@ -515,16 +565,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } void removeImeSurface() { - final IInputMethodManager imms = getImms(); - if (imms != null) { - try { - // Remove the IME surface to make the insets invisible for - // non-client controlled insets. - imms.removeImeSurface(); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to remove IME surface.", e); - } - } + // Remove the IME surface to make the insets invisible for + // non-client controlled insets. + InputMethodManagerGlobal.removeImeSurface( + e -> Slog.e(TAG, "Failed to remove IME surface.", e)); } /** @@ -598,11 +642,6 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } - public IInputMethodManager getImms() { - return IInputMethodManager.Stub.asInterface( - ServiceManager.getService(Context.INPUT_METHOD_SERVICE)); - } - private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) { if (a == b) { return true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java index 90a01f8c5295..8759301f695b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.common; +import android.annotation.Nullable; import android.content.ComponentName; import android.os.RemoteException; import android.util.Slog; @@ -24,7 +25,8 @@ import android.view.IDisplayWindowInsetsController; import android.view.IWindowManager; import android.view.InsetsSourceControl; import android.view.InsetsState; -import android.view.InsetsVisibilities; +import android.view.WindowInsets.Type.InsetsType; +import android.view.inputmethod.ImeTracker; import androidx.annotation.BinderThread; @@ -156,34 +158,40 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan } } - private void showInsets(int types, boolean fromIme) { + private void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId); if (listeners == null) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER); return; } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER); for (OnInsetsChangedListener listener : listeners) { - listener.showInsets(types, fromIme); + listener.showInsets(types, fromIme, statsToken); } } - private void hideInsets(int types, boolean fromIme) { + private void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId); if (listeners == null) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER); return; } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER); for (OnInsetsChangedListener listener : listeners) { - listener.hideInsets(types, fromIme); + listener.hideInsets(types, fromIme, statsToken); } } private void topFocusedWindowChanged(ComponentName component, - InsetsVisibilities requestedVisibilities) { + @InsetsType int requestedVisibleTypes) { CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId); if (listeners == null) { return; } for (OnInsetsChangedListener listener : listeners) { - listener.topFocusedWindowChanged(component, requestedVisibilities); + listener.topFocusedWindowChanged(component, requestedVisibleTypes); } } @@ -192,9 +200,9 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan extends IDisplayWindowInsetsController.Stub { @Override public void topFocusedWindowChanged(ComponentName component, - InsetsVisibilities requestedVisibilities) throws RemoteException { + @InsetsType int requestedVisibleTypes) throws RemoteException { mMainExecutor.execute(() -> { - PerDisplay.this.topFocusedWindowChanged(component, requestedVisibilities); + PerDisplay.this.topFocusedWindowChanged(component, requestedVisibleTypes); }); } @@ -214,16 +222,18 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan } @Override - public void showInsets(int types, boolean fromIme) throws RemoteException { + public void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) throws RemoteException { mMainExecutor.execute(() -> { - PerDisplay.this.showInsets(types, fromIme); + PerDisplay.this.showInsets(types, fromIme, statsToken); }); } @Override - public void hideInsets(int types, boolean fromIme) throws RemoteException { + public void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) throws RemoteException { mMainExecutor.execute(() -> { - PerDisplay.this.hideInsets(types, fromIme); + PerDisplay.this.hideInsets(types, fromIme, statsToken); }); } } @@ -239,11 +249,13 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan /** * Called when top focused window changes to determine whether or not to take over insets * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false. + * * @param component The application component that is open in the top focussed window. - * @param requestedVisibilities The insets visibilities requested by the focussed window. + * @param requestedVisibleTypes The {@link InsetsType} requested visible by the focused + * window. */ default void topFocusedWindowChanged(ComponentName component, - InsetsVisibilities requestedVisibilities) {} + @InsetsType int requestedVisibleTypes) {} /** * Called when the window insets configuration has changed. @@ -259,17 +271,23 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan /** * Called when a set of insets source window should be shown by policy. * - * @param types internal insets types (WindowInsets.Type.InsetsType) to show + * @param types {@link InsetsType} to show * @param fromIme true if this request originated from IME (InputMethodService). + * @param statsToken the token tracking the current IME show request + * or {@code null} otherwise. */ - default void showInsets(int types, boolean fromIme) {} + default void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) {} /** * Called when a set of insets source window should be hidden by policy. * - * @param types internal insets types (WindowInsets.Type.InsetsType) to hide + * @param types {@link InsetsType} to hide * @param fromIme true if this request originated from IME (InputMethodService). + * @param statsToken the token tracking the current IME hide request + * or {@code null} otherwise. */ - default void hideInsets(int types, boolean fromIme) {} + default void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) {} } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java index 2a1bf0ee42ba..fad3dee1f927 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java @@ -19,6 +19,7 @@ package com.android.wm.shell.common; import android.graphics.PixelFormat; import android.graphics.Rect; import android.view.SurfaceControl; +import android.window.ScreenCapture; import java.util.function.Consumer; @@ -35,9 +36,9 @@ public class ScreenshotUtils { * @param consumer Consumer for the captured buffer */ public static void captureLayer(SurfaceControl sc, Rect crop, - Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) { - consumer.accept(SurfaceControl.captureLayers( - new SurfaceControl.LayerCaptureArgs.Builder(sc) + Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) { + consumer.accept(ScreenCapture.captureLayers( + new ScreenCapture.LayerCaptureArgs.Builder(sc) .setSourceCrop(crop) .setCaptureSecureLayers(true) .setAllowProtected(true) @@ -45,7 +46,7 @@ public class ScreenshotUtils { } private static class BufferConsumer implements - Consumer<SurfaceControl.ScreenshotHardwareBuffer> { + Consumer<ScreenCapture.ScreenshotHardwareBuffer> { SurfaceControl mScreenshot = null; SurfaceControl.Transaction mTransaction; SurfaceControl mSurfaceControl; @@ -61,7 +62,7 @@ public class ScreenshotUtils { } @Override - public void accept(SurfaceControl.ScreenshotHardwareBuffer buffer) { + public void accept(ScreenCapture.ScreenshotHardwareBuffer buffer) { if (buffer == null || buffer.getHardwareBuffer() == null) { return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index e270edb800bd..5e46023cd84f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -19,6 +19,7 @@ package com.android.wm.shell.common; import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.graphics.Region; @@ -46,6 +47,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowlessWindowManager; +import android.view.inputmethod.ImeTracker; import android.window.ClientWindowFrames; import com.android.internal.os.IResultReceiver; @@ -304,7 +306,9 @@ public class SystemWindows { } } - protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + @Override + protected SurfaceControl getParentSurface(IWindow window, + WindowManager.LayoutParams attrs) { SurfaceControl leash = new SurfaceControl.Builder(new SurfaceSession()) .setContainerLayer() .setName("SystemWindowLeash") @@ -314,7 +318,7 @@ public class SystemWindows { synchronized (this) { mLeashForWindow.put(window.asBinder(), leash); } - b.setParent(leash); + return leash; } @Override @@ -344,17 +348,17 @@ public class SystemWindows { public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration newMergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, - int resizeMode) {} + boolean dragResizing) {} @Override public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls) {} @Override - public void showInsets(int types, boolean fromIme) {} + public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {} @Override - public void hideInsets(int types, boolean fromIme) {} + public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {} @Override public void moved(int newX, int newY) {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java index a09aab666a31..8ba785a1f03a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 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. @@ -14,11 +14,13 @@ * limitations under the License. */ -package com.android.wm.shell.pip.tv; +package com.android.wm.shell.common; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -28,36 +30,34 @@ import android.widget.RelativeLayout; import com.android.wm.shell.R; /** - * A View that represents Pip Menu action button, such as "Fullscreen" and "Close" as well custom - * (provided by the application in Pip) and media buttons. + * A common action button for TV window menu layouts. */ -public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener { +public class TvWindowMenuActionButton extends RelativeLayout { private final ImageView mIconImageView; private final View mButtonBackgroundView; - private final View mButtonView; - private OnClickListener mOnClickListener; - public TvPipMenuActionButton(Context context) { + private Icon mCurrentIcon; + + public TvWindowMenuActionButton(Context context) { this(context, null, 0, 0); } - public TvPipMenuActionButton(Context context, AttributeSet attrs) { + public TvWindowMenuActionButton(Context context, AttributeSet attrs) { this(context, attrs, 0, 0); } - public TvPipMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) { + public TvWindowMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } - public TvPipMenuActionButton( + public TvWindowMenuActionButton( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); final LayoutInflater inflater = (LayoutInflater) getContext() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.tv_pip_menu_action_button, this); + inflater.inflate(R.layout.tv_window_menu_action_button, this); mIconImageView = findViewById(R.id.icon); - mButtonView = findViewById(R.id.button); mButtonBackgroundView = findViewById(R.id.background); final int[] values = new int[]{android.R.attr.src, android.R.attr.text}; @@ -72,23 +72,6 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic typedArray.recycle(); } - @Override - public void setOnClickListener(OnClickListener listener) { - // We do not want to set an OnClickListener to the TvPipMenuActionButton itself, but only to - // the ImageView. So let's "cash" the listener we've been passed here and set a "proxy" - // listener to the ImageView. - mOnClickListener = listener; - mButtonView.setOnClickListener(listener != null ? this : null); - } - - @Override - public void onClick(View v) { - if (mOnClickListener != null) { - // Pass the correct view - this. - mOnClickListener.onClick(this); - } - } - /** * Sets the drawable for the button with the given drawable. */ @@ -105,11 +88,24 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic } } + public void setImageIconAsync(Icon icon, Handler handler) { + mCurrentIcon = icon; + // Remove old image while waiting for the new one to load. + mIconImageView.setImageDrawable(null); + icon.loadDrawableAsync(mContext, d -> { + // The image hasn't been set any other way and the drawable belongs to the most + // recently set Icon. + if (mIconImageView.getDrawable() == null && mCurrentIcon == icon) { + mIconImageView.setImageDrawable(d); + } + }, handler); + } + /** * Sets the text for description the with the given string. */ public void setTextAndDescription(CharSequence text) { - mButtonView.setContentDescription(text); + setContentDescription(text); } /** @@ -119,32 +115,29 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic setTextAndDescription(getContext().getString(resId)); } - @Override - public void setEnabled(boolean enabled) { - mButtonView.setEnabled(enabled); - } - - @Override - public boolean isEnabled() { - return mButtonView.isEnabled(); - } - - void setIsCustomCloseAction(boolean isCustomCloseAction) { + /** + * Marks this button as a custom close action button. + * This changes the style of the action button to highlight that this action finishes the + * Picture-in-Picture activity. + * + * @param isCustomCloseAction sets or unsets this button as a custom close action button. + */ + public void setIsCustomCloseAction(boolean isCustomCloseAction) { mIconImageView.setImageTintList( getResources().getColorStateList( - isCustomCloseAction ? R.color.tv_pip_menu_close_icon - : R.color.tv_pip_menu_icon)); + isCustomCloseAction ? R.color.tv_window_menu_close_icon + : R.color.tv_window_menu_icon)); mButtonBackgroundView.setBackgroundTintList(getResources() - .getColorStateList(isCustomCloseAction ? R.color.tv_pip_menu_close_icon_bg - : R.color.tv_pip_menu_icon_bg)); + .getColorStateList(isCustomCloseAction ? R.color.tv_window_menu_close_icon_bg + : R.color.tv_window_menu_icon_bg)); } @Override public String toString() { - if (mButtonView.getContentDescription() == null) { - return TvPipMenuActionButton.class.getSimpleName(); + if (getContentDescription() == null) { + return TvWindowMenuActionButton.class.getSimpleName(); } - return mButtonView.getContentDescription().toString(); + return getContentDescription().toString(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index a9d3c9f154cd..fcbf9e0812b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -92,7 +92,7 @@ public class SplitDecorManager extends WindowlessWindowManager { } @Override - protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) { // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later. final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) .setContainerLayer() @@ -101,7 +101,7 @@ public class SplitDecorManager extends WindowlessWindowManager { .setParent(mHostLeash) .setCallsite("SplitDecorManager#attachToParentSurface"); mIconLeash = builder.build(); - b.setParent(mIconLeash); + return mIconLeash; } /** Inflates split decor surface on the root surface. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java index 060ac56cae96..6b5ddcb7ebe3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java @@ -93,7 +93,7 @@ public final class SplitWindowManager extends WindowlessWindowManager { } @Override - protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) { // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later. final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) .setContainerLayer() @@ -103,7 +103,7 @@ public final class SplitWindowManager extends WindowlessWindowManager { mParentContainerCallbacks.attachToParentSurface(builder); mLeash = builder.build(); mParentContainerCallbacks.onLeashReady(mLeash); - b.setParent(mLeash); + return mLeash; } /** Inflates {@link DividerView} on to the root surface. */ @@ -126,6 +126,7 @@ public final class SplitWindowManager extends WindowlessWindowManager { lp.token = new Binder(); lp.setTitle(mWindowName); lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; + lp.accessibilityTitle = mContext.getResources().getString(R.string.accessibility_divider); mViewHost.setView(mDividerView, lp); mDividerView.setup(splitLayout, this, mViewHost, insetsState); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java index face24340a4e..2cc9f45d1e9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java @@ -155,7 +155,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana } @Override - protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) { String className = getClass().getSimpleName(); final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) .setContainerLayer() @@ -164,9 +164,8 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana .setCallsite(className + "#attachToParentSurface"); attachToParentSurface(builder); mLeash = builder.build(); - b.setParent(mLeash); - initSurface(mLeash); + return mLeash; } /** Inits the z-order of the surface. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index 8022e9b1cd81..b144d22fc3ee 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -81,6 +81,7 @@ public abstract class TvPipModule { PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper windowManagerShellWrapper, + @ShellMainThread Handler mainHandler, // needed for registerReceiverForAllUsers() @ShellMainThread ShellExecutor mainExecutor) { return Optional.of( TvPipController.create( @@ -100,6 +101,7 @@ public abstract class TvPipModule { pipParamsChangedForwarder, displayController, windowManagerShellWrapper, + mainHandler, mainExecutor)); } @@ -157,22 +159,17 @@ public abstract class TvPipModule { Context context, TvPipBoundsState tvPipBoundsState, SystemWindows systemWindows, - PipMediaController pipMediaController, @ShellMainThread Handler mainHandler) { - return new TvPipMenuController(context, tvPipBoundsState, systemWindows, pipMediaController, - mainHandler); + return new TvPipMenuController(context, tvPipBoundsState, systemWindows, mainHandler); } - // Handler needed for registerReceiverForAllUsers() @WMSingleton @Provides static TvPipNotificationController provideTvPipNotificationController(Context context, PipMediaController pipMediaController, - PipParamsChangedForwarder pipParamsChangedForwarder, - TvPipBoundsState tvPipBoundsState, - @ShellMainThread Handler mainHandler) { + PipParamsChangedForwarder pipParamsChangedForwarder) { return new TvPipNotificationController(context, pipMediaController, - pipParamsChangedForwarder, tvPipBoundsState, mainHandler); + pipParamsChangedForwarder); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java index 15bfeb297b41..e9957fd4f4f1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java @@ -16,16 +16,32 @@ package com.android.wm.shell.dagger; -import android.view.IWindowManager; +import android.content.Context; +import android.os.Handler; +import com.android.launcher3.icons.IconProvider; +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.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.draganddrop.DragAndDropController; +import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.splitscreen.tv.TvSplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm; import com.android.wm.shell.startingsurface.tv.TvStartingWindowTypeAlgorithm; +import com.android.wm.shell.sysui.ShellCommandHandler; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +import java.util.Optional; import dagger.Module; import dagger.Provides; @@ -50,5 +66,33 @@ public class TvWMShellModule { @DynamicOverride static StartingWindowTypeAlgorithm provideStartingWindowTypeAlgorithm() { return new TvStartingWindowTypeAlgorithm(); - }; + } + + @WMSingleton + @Provides + @DynamicOverride + static SplitScreenController provideSplitScreenController(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, + ShellController shellController, + ShellTaskOrganizer shellTaskOrganizer, + SyncTransactionQueue syncQueue, + RootTaskDisplayAreaOrganizer rootTDAOrganizer, + DisplayController displayController, + DisplayImeController displayImeController, + DisplayInsetsController displayInsetsController, + DragAndDropController dragAndDropController, + Transitions transitions, + TransactionPool transactionPool, + IconProvider iconProvider, + Optional<RecentTasksController> recentTasks, + @ShellMainThread ShellExecutor mainExecutor, + Handler mainHandler, + SystemWindows systemWindows) { + return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController, + shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController, + displayImeController, displayInsetsController, dragAndDropController, transitions, + transactionPool, iconProvider, recentTasks, mainExecutor, mainHandler, + systemWindows); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 09f5cf1d31e4..7055aca0ae8a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -38,6 +38,7 @@ import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.back.BackAnimation; +import com.android.wm.shell.back.BackAnimationBackground; import com.android.wm.shell.back.BackAnimationController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.Bubbles; @@ -94,13 +95,13 @@ import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import com.android.wm.shell.windowdecor.WindowDecorViewModel; -import java.util.Optional; - import dagger.BindsOptionalOf; import dagger.Lazy; import dagger.Module; import dagger.Provides; +import java.util.Optional; + /** * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only * accessible from components within the WM subcomponent (can be explicitly exposed to the @@ -256,21 +257,30 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides + static BackAnimationBackground provideBackAnimationBackground( + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + return new BackAnimationBackground(rootTaskDisplayAreaOrganizer); + } + + @WMSingleton + @Provides static Optional<BackAnimationController> provideBackAnimationController( Context context, ShellInit shellInit, ShellController shellController, @ShellMainThread ShellExecutor shellExecutor, - @ShellBackgroundThread Handler backgroundHandler + @ShellBackgroundThread Handler backgroundHandler, + BackAnimationBackground backAnimationBackground ) { if (BackAnimationController.IS_ENABLED) { return Optional.of( new BackAnimationController(shellInit, shellController, shellExecutor, - backgroundHandler, context)); + backgroundHandler, context, backAnimationBackground)); } return Optional.empty(); } + // // Bubbles (optional feature) // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java index b310ee2095bf..8ebcd815cf12 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java @@ -104,7 +104,7 @@ public final class BackgroundWindowManager extends WindowlessWindowManager { } @Override - protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) { final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) .setColorLayer() .setBufferSize(mDisplayBounds.width(), mDisplayBounds.height()) @@ -113,7 +113,7 @@ public final class BackgroundWindowManager extends WindowlessWindowManager { .setName(TAG) .setCallsite("BackgroundWindowManager#attachToParentSurface"); mLeash = builder.build(); - b.setParent(mLeash); + return mLeash; } /** Inflates background view on to the root surface. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java index 16f1d1c2944c..000624499f79 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java @@ -26,11 +26,14 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.RemoteAction; +import android.content.Context; import android.graphics.PixelFormat; import android.graphics.Rect; import android.view.SurfaceControl; import android.view.WindowManager; +import com.android.wm.shell.R; + import java.util.List; /** @@ -97,16 +100,20 @@ public interface PipMenuController { /** * Returns a default LayoutParams for the PIP Menu. + * @param context the context. * @param width the PIP stack width. * @param height the PIP stack height. */ - default WindowManager.LayoutParams getPipMenuLayoutParams(String title, int width, int height) { + default WindowManager.LayoutParams getPipMenuLayoutParams(Context context, String title, + int width, int height) { final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height, TYPE_APPLICATION_OVERLAY, FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); lp.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY; lp.setTitle(title); + lp.accessibilityTitle = context.getResources().getString( + R.string.pip_menu_accessibility_title); return lp; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 431bd7b08142..94e593b106a5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -186,7 +186,7 @@ public class PhonePipMenuController implements PipMenuController { mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler, mSplitScreenController, mPipUiEventLogger); mSystemWindows.addView(mPipMenuView, - getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), + getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), 0, SHELL_ROOT_LAYER_PIP); setShellRootAccessibilityWindow(); @@ -210,7 +210,7 @@ public class PhonePipMenuController implements PipMenuController { @Override public void updateMenuBounds(Rect destinationBounds) { mSystemWindows.updateViewLayout(mPipMenuView, - getPipMenuLayoutParams(MENU_WINDOW_TITLE, destinationBounds.width(), + getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, destinationBounds.width(), destinationBounds.height())); updateMenuLayout(destinationBounds); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java index 7365b9525919..4a06d84ce90d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java @@ -24,11 +24,13 @@ import android.graphics.Region; import android.os.Bundle; import android.os.RemoteException; import android.view.MagnificationSpec; +import android.view.SurfaceControl; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import android.window.ScreenCapture; import androidx.annotation.BinderThread; @@ -362,6 +364,15 @@ public class PipAccessibilityInteractionConnection { } @Override + public void takeScreenshotOfWindow(int interactionId, + ScreenCapture.ScreenCaptureListener listener, + IAccessibilityInteractionConnectionCallback callback) throws RemoteException { + // AbstractAccessibilityServiceConnection uses the standard + // IAccessibilityInteractionConnection for takeScreenshotOfWindow for Pip windows, + // so do nothing here. + } + + @Override public void clearAccessibilityFocus() throws RemoteException { // Do nothing } @@ -370,5 +381,8 @@ public class PipAccessibilityInteractionConnection { public void notifyOutsideTouch() throws RemoteException { // Do nothing } - } + + @Override + public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {} } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java index acc0caf95e35..d7d335b856be 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java @@ -43,16 +43,16 @@ public class PipDoubleTapHelper { * <p>MAX - maximum allowed screen size</p> */ @IntDef(value = { - SIZE_SPEC_CUSTOM, SIZE_SPEC_DEFAULT, - SIZE_SPEC_MAX + SIZE_SPEC_MAX, + SIZE_SPEC_CUSTOM }) @Retention(RetentionPolicy.SOURCE) @interface PipSizeSpec {} - static final int SIZE_SPEC_CUSTOM = 2; static final int SIZE_SPEC_DEFAULT = 0; static final int SIZE_SPEC_MAX = 1; + static final int SIZE_SPEC_CUSTOM = 2; /** * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS index 85441af9a870..5aa3c4e2abef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS @@ -1,2 +1,3 @@ # WM shell sub-module TV pip owner galinap@google.com +bronger@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java new file mode 100644 index 000000000000..222307fba8c2 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2022 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.pip.tv; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.os.Handler; + +import com.android.wm.shell.common.TvWindowMenuActionButton; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +abstract class TvPipAction { + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"ACTION_"}, value = { + ACTION_FULLSCREEN, + ACTION_CLOSE, + ACTION_MOVE, + ACTION_EXPAND_COLLAPSE, + ACTION_CUSTOM, + ACTION_CUSTOM_CLOSE + }) + public @interface ActionType { + } + + public static final int ACTION_FULLSCREEN = 0; + public static final int ACTION_CLOSE = 1; + public static final int ACTION_MOVE = 2; + public static final int ACTION_EXPAND_COLLAPSE = 3; + public static final int ACTION_CUSTOM = 4; + public static final int ACTION_CUSTOM_CLOSE = 5; + + @ActionType + private final int mActionType; + + @NonNull + private final SystemActionsHandler mSystemActionsHandler; + + TvPipAction(@ActionType int actionType, @NonNull SystemActionsHandler systemActionsHandler) { + Objects.requireNonNull(systemActionsHandler); + mActionType = actionType; + mSystemActionsHandler = systemActionsHandler; + } + + boolean isCloseAction() { + return mActionType == ACTION_CLOSE || mActionType == ACTION_CUSTOM_CLOSE; + } + + @ActionType + int getActionType() { + return mActionType; + } + + abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler); + + abstract PendingIntent getPendingIntent(); + + void executeAction() { + mSystemActionsHandler.executeAction(mActionType); + } + + abstract Notification.Action toNotificationAction(Context context); + + interface SystemActionsHandler { + void executeAction(@TvPipAction.ActionType int actionType); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java new file mode 100644 index 000000000000..fa62a73ca9b4 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2022 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.pip.tv; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE; +import static com.android.wm.shell.pip.tv.TvPipController.ACTION_CLOSE_PIP; +import static com.android.wm.shell.pip.tv.TvPipController.ACTION_MOVE_PIP; +import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TOGGLE_EXPANDED_PIP; +import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TO_FULLSCREEN; + +import android.annotation.NonNull; +import android.app.RemoteAction; +import android.content.Context; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; +import com.android.wm.shell.pip.PipMediaController; +import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +import java.util.ArrayList; +import java.util.List; + +/** + * Creates the system TvPipActions (fullscreen, close, move, expand/collapse), and handles all the + * changes to the actions, including the custom app actions and media actions. Other components can + * listen to those changes. + */ +public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler { + private static final String TAG = TvPipActionsProvider.class.getSimpleName(); + + private static final int CLOSE_ACTION_INDEX = 1; + private static final int FIRST_CUSTOM_ACTION_INDEX = 2; + + private final List<Listener> mListeners = new ArrayList<>(); + private final TvPipAction.SystemActionsHandler mSystemActionsHandler; + + private final List<TvPipAction> mActionsList; + private final TvPipSystemAction mDefaultCloseAction; + private final TvPipSystemAction mExpandCollapseAction; + + private final List<RemoteAction> mMediaActions = new ArrayList<>(); + private final List<RemoteAction> mAppActions = new ArrayList<>(); + + public TvPipActionsProvider(Context context, PipMediaController pipMediaController, + TvPipAction.SystemActionsHandler systemActionsHandler) { + mSystemActionsHandler = systemActionsHandler; + + mActionsList = new ArrayList<>(); + mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen, + R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context, + mSystemActionsHandler)); + + mDefaultCloseAction = new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close, + R.drawable.pip_ic_close_white, ACTION_CLOSE_PIP, context, mSystemActionsHandler); + mActionsList.add(mDefaultCloseAction); + + mActionsList.add(new TvPipSystemAction(ACTION_MOVE, R.string.pip_move, + R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler)); + + mExpandCollapseAction = new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse, + R.drawable.pip_ic_collapse, ACTION_TOGGLE_EXPANDED_PIP, context, + mSystemActionsHandler); + mActionsList.add(mExpandCollapseAction); + + pipMediaController.addActionListener(this::onMediaActionsChanged); + } + + @Override + public void executeAction(@TvPipAction.ActionType int actionType) { + if (mSystemActionsHandler != null) { + mSystemActionsHandler.executeAction(actionType); + } + } + + private void notifyActionsChanged(int added, int changed, int startIndex) { + for (Listener listener : mListeners) { + listener.onActionsChanged(added, changed, startIndex); + } + } + + @VisibleForTesting(visibility = PACKAGE) + public void setAppActions(@NonNull List<RemoteAction> appActions, RemoteAction closeAction) { + // Update close action. + mActionsList.set(CLOSE_ACTION_INDEX, + closeAction == null ? mDefaultCloseAction + : new TvPipCustomAction(ACTION_CUSTOM_CLOSE, closeAction, + mSystemActionsHandler)); + notifyActionsChanged(/* added= */ 0, /* updated= */ 1, CLOSE_ACTION_INDEX); + + // Replace custom actions with new ones. + mAppActions.clear(); + for (RemoteAction action : appActions) { + if (action != null && !PipUtils.remoteActionsMatch(action, closeAction)) { + // Only show actions that aren't duplicates of the custom close action. + mAppActions.add(action); + } + } + + updateCustomActions(mAppActions); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public void onMediaActionsChanged(List<RemoteAction> actions) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onMediaActionsChanged()", TAG); + + mMediaActions.clear(); + // Don't show disabled actions. + for (RemoteAction remoteAction : actions) { + if (remoteAction.isEnabled()) { + mMediaActions.add(remoteAction); + } + } + + updateCustomActions(mMediaActions); + } + + private void updateCustomActions(@NonNull List<RemoteAction> customActions) { + List<RemoteAction> newCustomActions = customActions; + if (newCustomActions == mMediaActions && !mAppActions.isEmpty()) { + // Don't show the media actions while there are app actions. + return; + } else if (newCustomActions == mAppActions && mAppActions.isEmpty()) { + // If all the app actions were removed, show the media actions. + newCustomActions = mMediaActions; + } + + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: replaceCustomActions, count: %d", TAG, newCustomActions.size()); + int oldCustomActionsCount = 0; + for (TvPipAction action : mActionsList) { + if (action.getActionType() == ACTION_CUSTOM) { + oldCustomActionsCount++; + } + } + mActionsList.removeIf(tvPipAction -> tvPipAction.getActionType() == ACTION_CUSTOM); + + List<TvPipAction> actions = new ArrayList<>(); + for (RemoteAction action : newCustomActions) { + actions.add(new TvPipCustomAction(ACTION_CUSTOM, action, mSystemActionsHandler)); + } + mActionsList.addAll(FIRST_CUSTOM_ACTION_INDEX, actions); + + int added = newCustomActions.size() - oldCustomActionsCount; + int changed = Math.min(newCustomActions.size(), oldCustomActionsCount); + notifyActionsChanged(added, changed, FIRST_CUSTOM_ACTION_INDEX); + } + + @VisibleForTesting(visibility = PACKAGE) + public void updateExpansionEnabled(boolean enabled) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: updateExpansionState, enabled: %b", TAG, enabled); + int actionIndex = mActionsList.indexOf(mExpandCollapseAction); + boolean actionInList = actionIndex != -1; + if (enabled && !actionInList) { + mActionsList.add(mExpandCollapseAction); + actionIndex = mActionsList.size() - 1; + } else if (!enabled && actionInList) { + mActionsList.remove(actionIndex); + } else { + return; + } + notifyActionsChanged(/* added= */ enabled ? 1 : -1, /* updated= */ 0, actionIndex); + } + + @VisibleForTesting(visibility = PACKAGE) + public void onPipExpansionToggled(boolean expanded) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onPipExpansionToggled, expanded: %b", TAG, expanded); + + mExpandCollapseAction.update( + expanded ? R.string.pip_collapse : R.string.pip_expand, + expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand); + + notifyActionsChanged(/* added= */ 0, /* updated= */ 1, + mActionsList.indexOf(mExpandCollapseAction)); + } + + List<TvPipAction> getActionsList() { + return mActionsList; + } + + @NonNull + TvPipAction getCloseAction() { + return mActionsList.get(CLOSE_ACTION_INDEX); + } + + void addListener(Listener listener) { + if (!mListeners.contains(listener)) { + mListeners.add(listener); + } + } + + /** + * Returns the index of the first action of the given action type or -1 if none can be found. + */ + int getFirstIndexOfAction(@TvPipAction.ActionType int actionType) { + for (int i = 0; i < mActionsList.size(); i++) { + if (mActionsList.get(i).getActionType() == actionType) { + return i; + } + } + return -1; + } + + /** + * Allow components to listen to updates to the actions list, including where they happen so + * that changes can be animated. + */ + interface Listener { + /** + * Notifies the listener how many actions were added/removed or updated. + * + * @param added can be positive (number of actions added), negative (number of actions + * removed) or zero (the number of actions stayed the same). + * @param updated the number of actions that might have been updated and need to be + * refreshed. + * @param startIndex The index of the first updated action. The added/removed actions start + * at (startIndex + updated). + */ + void onActionsChanged(int added, int updated, int startIndex); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index ce34d2f9547d..31490e427a53 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -22,14 +22,12 @@ import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT; import static android.view.KeyEvent.KEYCODE_DPAD_UP; import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_HORIZONTAL; -import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_UNDETERMINED; import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_VERTICAL; import android.content.Context; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Rect; -import android.util.ArraySet; import android.util.Size; import android.view.Gravity; @@ -50,11 +48,10 @@ import java.util.Set; * Contains pip bounds calculations that are specific to TV. */ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { - private static final String TAG = TvPipBoundsAlgorithm.class.getSimpleName(); - private static final boolean DEBUG = TvPipController.DEBUG; - private final @NonNull TvPipBoundsState mTvPipBoundsState; + @NonNull + private final TvPipBoundsState mTvPipBoundsState; private int mFixedExpandedHeightInPx; private int mFixedExpandedWidthInPx; @@ -65,7 +62,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { @NonNull TvPipBoundsState tvPipBoundsState, @NonNull PipSnapAlgorithm pipSnapAlgorithm) { super(context, tvPipBoundsState, pipSnapAlgorithm, - new PipKeepClearAlgorithm() {}); + new PipKeepClearAlgorithm() { + }); this.mTvPipBoundsState = tvPipBoundsState; this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(); reloadResources(context); @@ -92,16 +90,15 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { /** Returns the destination bounds to place the PIP window on entry. */ @Override public Rect getEntryDestinationBounds() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: getEntryDestinationBounds()", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: getEntryDestinationBounds()", TAG); + updateExpandedPipSize(); final boolean isPipExpanded = mTvPipBoundsState.isTvExpandedPipSupported() && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0 && !mTvPipBoundsState.isTvPipManuallyCollapsed(); if (isPipExpanded) { - updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true); + updateGravityOnExpansionToggled(/* expanding= */ true); } mTvPipBoundsState.setTvPipExpanded(isPipExpanded); return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds()); @@ -110,10 +107,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { /** Returns the current bounds adjusted to the new aspect ratio, if valid. */ @Override public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio); return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds()); } @@ -141,25 +136,9 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { final Rect insetBounds = new Rect(); getInsetBounds(insetBounds); - Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas(); - Set<Rect> unrestrictedKeepClearAreas = mTvPipBoundsState.getUnrestrictedKeepClearAreas(); - - if (mTvPipBoundsState.isImeShowing()) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: IME showing, height: %d", - TAG, mTvPipBoundsState.getImeHeight()); - } - - final Rect imeBounds = new Rect( - 0, - insetBounds.bottom - mTvPipBoundsState.getImeHeight(), - insetBounds.right, - insetBounds.bottom); - - unrestrictedKeepClearAreas = new ArraySet<>(unrestrictedKeepClearAreas); - unrestrictedKeepClearAreas.add(imeBounds); - } + final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas(); + final Set<Rect> unrestrictedKeepClearAreas = + mTvPipBoundsState.getUnrestrictedKeepClearAreas(); mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity()); mKeepClearAlgorithm.setScreenSize(screenSize); @@ -173,165 +152,105 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { restrictedKeepClearAreas, unrestrictedKeepClearAreas); - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: screenSize: %s", TAG, screenSize); - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: stashOffset: %d", TAG, mTvPipBoundsState.getStashOffset()); - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: insetBounds: %s", TAG, insetBounds.toShortString()); - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: pipSize: %s", TAG, pipSize); - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: gravity: %s", TAG, Gravity.toString(mTvPipBoundsState.getTvPipGravity())); - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: restrictedKeepClearAreas: %s", TAG, restrictedKeepClearAreas); - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: unrestrictedKeepClearAreas: %s", TAG, unrestrictedKeepClearAreas); - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: placement: %s", TAG, placement); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: screenSize: %s", TAG, screenSize); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: stashOffset: %d", TAG, mTvPipBoundsState.getStashOffset()); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: insetBounds: %s", TAG, insetBounds.toShortString()); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: pipSize: %s", TAG, pipSize); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: gravity: %s", TAG, Gravity.toString(mTvPipBoundsState.getTvPipGravity())); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: restrictedKeepClearAreas: %s", TAG, restrictedKeepClearAreas); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: unrestrictedKeepClearAreas: %s", TAG, unrestrictedKeepClearAreas); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: placement: %s", TAG, placement); return placement; } - /** - * @return previous gravity if it is to be saved, or {@link Gravity#NO_GRAVITY} if not. - */ - int updateGravityOnExpandToggled(int previousGravity, boolean expanding) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: updateGravityOnExpandToggled(), expanding: %b" - + ", mOrientation: %d, previous gravity: %s", - TAG, expanding, mTvPipBoundsState.getTvFixedPipOrientation(), - Gravity.toString(previousGravity)); - } - - if (!mTvPipBoundsState.isTvExpandedPipSupported()) { - return Gravity.NO_GRAVITY; - } + void updateGravityOnExpansionToggled(boolean expanding) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: updateGravity, expanding: %b, fixedExpandedOrientation: %d", + TAG, expanding, mTvPipBoundsState.getTvFixedPipOrientation()); - if (expanding && mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_UNDETERMINED) { - float expandedRatio = mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); - if (expandedRatio == 0) { - return Gravity.NO_GRAVITY; - } - if (expandedRatio < 1) { - mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_VERTICAL); - } else { - mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_HORIZONTAL); - } - } + int currentX = mTvPipBoundsState.getTvPipGravity() & Gravity.HORIZONTAL_GRAVITY_MASK; + int currentY = mTvPipBoundsState.getTvPipGravity() & Gravity.VERTICAL_GRAVITY_MASK; + int previousCollapsedX = mTvPipBoundsState.getTvPipPreviousCollapsedGravity() + & Gravity.HORIZONTAL_GRAVITY_MASK; + int previousCollapsedY = mTvPipBoundsState.getTvPipPreviousCollapsedGravity() + & Gravity.VERTICAL_GRAVITY_MASK; - int gravityToSave = Gravity.NO_GRAVITY; - int currentGravity = mTvPipBoundsState.getTvPipGravity(); int updatedGravity; - if (expanding) { - // save collapsed gravity - gravityToSave = mTvPipBoundsState.getTvPipGravity(); + // Save collapsed gravity. + mTvPipBoundsState.setTvPipPreviousCollapsedGravity(mTvPipBoundsState.getTvPipGravity()); if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) { - updatedGravity = - Gravity.CENTER_HORIZONTAL | (currentGravity - & Gravity.VERTICAL_GRAVITY_MASK); + updatedGravity = Gravity.CENTER_HORIZONTAL | currentY; } else { - updatedGravity = - Gravity.CENTER_VERTICAL | (currentGravity - & Gravity.HORIZONTAL_GRAVITY_MASK); + updatedGravity = currentX | Gravity.CENTER_VERTICAL; } } else { - if (previousGravity != Gravity.NO_GRAVITY) { - // The pip hasn't been moved since expanding, - // go back to previous collapsed position. - updatedGravity = previousGravity; + // Collapse to the edge that the user moved to before. + if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) { + updatedGravity = previousCollapsedX | currentY; } else { - if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) { - updatedGravity = - Gravity.RIGHT | (currentGravity & Gravity.VERTICAL_GRAVITY_MASK); - } else { - updatedGravity = - Gravity.BOTTOM | (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK); - } + updatedGravity = currentX | previousCollapsedY; } } mTvPipBoundsState.setTvPipGravity(updatedGravity); - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity)); - } - - return gravityToSave; + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity)); } /** - * @return true if gravity changed + * @return true if the gravity changed */ boolean updateGravity(int keycode) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: updateGravity, keycode: %d", TAG, keycode); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: updateGravity, keycode: %d", TAG, keycode); - // Check if position change is valid + // Check if position change is valid. if (mTvPipBoundsState.isTvPipExpanded()) { - int mOrientation = mTvPipBoundsState.getTvFixedPipOrientation(); - if (mOrientation == ORIENTATION_VERTICAL + int fixedOrientation = mTvPipBoundsState.getTvFixedPipOrientation(); + if (fixedOrientation == ORIENTATION_VERTICAL && (keycode == KEYCODE_DPAD_UP || keycode == KEYCODE_DPAD_DOWN) - || mOrientation == ORIENTATION_HORIZONTAL + || fixedOrientation == ORIENTATION_HORIZONTAL && (keycode == KEYCODE_DPAD_RIGHT || keycode == KEYCODE_DPAD_LEFT)) { return false; } } - int currentGravity = mTvPipBoundsState.getTvPipGravity(); - int updatedGravity; - // First axis + int updatedX = mTvPipBoundsState.getTvPipGravity() & Gravity.HORIZONTAL_GRAVITY_MASK; + int updatedY = mTvPipBoundsState.getTvPipGravity() & Gravity.VERTICAL_GRAVITY_MASK; + switch (keycode) { case KEYCODE_DPAD_UP: - updatedGravity = Gravity.TOP; + updatedY = Gravity.TOP; break; case KEYCODE_DPAD_DOWN: - updatedGravity = Gravity.BOTTOM; + updatedY = Gravity.BOTTOM; break; case KEYCODE_DPAD_LEFT: - updatedGravity = Gravity.LEFT; + updatedX = Gravity.LEFT; break; case KEYCODE_DPAD_RIGHT: - updatedGravity = Gravity.RIGHT; + updatedX = Gravity.RIGHT; break; default: - updatedGravity = currentGravity; + // NOOP - unsupported keycode } - // Second axis - switch (keycode) { - case KEYCODE_DPAD_UP: - case KEYCODE_DPAD_DOWN: - if (mTvPipBoundsState.isTvPipExpanded()) { - updatedGravity |= Gravity.CENTER_HORIZONTAL; - } else { - updatedGravity |= (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK); - } - break; - case KEYCODE_DPAD_LEFT: - case KEYCODE_DPAD_RIGHT: - if (mTvPipBoundsState.isTvPipExpanded()) { - updatedGravity |= Gravity.CENTER_VERTICAL; - } else { - updatedGravity |= (currentGravity & Gravity.VERTICAL_GRAVITY_MASK); - } - break; - default: - break; - } + int updatedGravity = updatedX | updatedY; - if (updatedGravity != currentGravity) { + if (updatedGravity != mTvPipBoundsState.getTvPipGravity()) { mTvPipBoundsState.setTvPipGravity(updatedGravity); - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity)); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: updateGravity, new gravity: %s", TAG, Gravity.toString(updatedGravity)); return true; } return false; @@ -362,8 +281,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { final Size expandedSize; if (expandedRatio == 0) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: updateExpandedPipSize(): Expanded mode aspect ratio" - + " of 0 not supported", TAG); + "%s: updateExpandedPipSize(): Expanded mode aspect ratio" + + " of 0 not supported", TAG); return; } else if (expandedRatio < 1) { // vertical @@ -375,16 +294,12 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio; if (maxHeight > aspectRatioHeight) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Accommodate aspect ratio", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Accommodate aspect ratio", TAG); expandedSize = new Size(mFixedExpandedWidthInPx, (int) aspectRatioHeight); } else { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Aspect ratio is too extreme, use max size", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Aspect ratio is too extreme, use max size", TAG); expandedSize = new Size(mFixedExpandedWidthInPx, maxHeight); } } @@ -397,26 +312,20 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { - pipDecorations.left - pipDecorations.right; float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio; if (maxWidth > aspectRatioWidth) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Accommodate aspect ratio", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Accommodate aspect ratio", TAG); expandedSize = new Size((int) aspectRatioWidth, mFixedExpandedHeightInPx); } else { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Aspect ratio is too extreme, use max size", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Aspect ratio is too extreme, use max size", TAG); expandedSize = new Size(maxWidth, mFixedExpandedHeightInPx); } } } mTvPipBoundsState.setTvExpandedSize(expandedSize); - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: updateExpandedPipSize(): expanded size, width: %d, height: %d", - TAG, expandedSize.getWidth(), expandedSize.getHeight()); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: updateExpandedPipSize(): expanded size, width: %d, height: %d", + TAG, expandedSize.getWidth(), expandedSize.getHeight()); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java index 3a6ce81821ec..b189163a354a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java @@ -39,7 +39,6 @@ import java.util.function.Supplier; * Manages debouncing of PiP movements and scheduling of unstashing. */ public class TvPipBoundsController { - private static final boolean DEBUG = false; private static final String TAG = "TvPipBoundsController"; /** @@ -122,9 +121,9 @@ public class TvPipBoundsController { cancelScheduledPlacement(); applyPlacementBounds(placement.getUnstashedBounds(), animationDuration); } else if (immediate) { + boolean shouldStash = mUnstashRunnable != null || placement.getTriggerStash(); cancelScheduledPlacement(); - applyPlacementBounds(placement.getBounds(), animationDuration); - scheduleUnstashIfNeeded(placement); + applyPlacement(placement, shouldStash, animationDuration); } else { applyPlacementBounds(mCurrentPlacementBounds, animationDuration); schedulePinnedStackPlacement(placement, animationDuration); @@ -133,11 +132,9 @@ public class TvPipBoundsController { private void schedulePinnedStackPlacement(@NonNull final Placement placement, int animationDuration) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: schedulePinnedStackPlacement() - pip bounds: %s", - TAG, placement.getBounds().toShortString()); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: schedulePinnedStackPlacement() - pip bounds: %s", + TAG, placement.getBounds().toShortString()); if (mPendingPlacement != null && Objects.equals(mPendingPlacement.getBounds(), placement.getBounds())) { @@ -171,27 +168,24 @@ public class TvPipBoundsController { } private void applyPendingPlacement() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: applyPendingPlacement()", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: applyPendingPlacement()", TAG); if (mPendingPlacement != null) { - if (mPendingStash) { - mPendingStash = false; - scheduleUnstashIfNeeded(mPendingPlacement); - } - - if (mUnstashRunnable != null) { - // currently stashed, use stashed pos - applyPlacementBounds(mPendingPlacement.getBounds(), - mPendingPlacementAnimationDuration); - } else { - applyPlacementBounds(mPendingPlacement.getUnstashedBounds(), - mPendingPlacementAnimationDuration); - } + applyPlacement(mPendingPlacement, mPendingStash, mPendingPlacementAnimationDuration); + mPendingStash = false; + mPendingPlacement = null; } + } - mPendingPlacement = null; + private void applyPlacement(@NonNull final Placement placement, boolean shouldStash, + int animationDuration) { + if (placement.getStashType() != STASH_TYPE_NONE && shouldStash) { + scheduleUnstashIfNeeded(placement); + } + + Rect bounds = + mUnstashRunnable != null ? placement.getBounds() : placement.getUnstashedBounds(); + applyPlacementBounds(bounds, animationDuration); } void onPipDismissed() { @@ -227,10 +221,8 @@ public class TvPipBoundsController { } mPipTargetBounds = bounds; - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: movePipTo() - new pip bounds: %s", TAG, bounds.toShortString()); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: movePipTo() - new pip bounds: %s", TAG, bounds.toShortString()); if (mListener != null) { mListener.onPipTargetBoundsChange(bounds, animationDuration); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java index ca22882187d8..9c7c0aef636d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java @@ -27,6 +27,7 @@ import android.content.pm.PackageManager; import android.graphics.Insets; import android.util.Size; import android.view.Gravity; +import android.view.View; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; @@ -52,24 +53,61 @@ public class TvPipBoundsState extends PipBoundsState { public @interface Orientation { } - public static final int DEFAULT_TV_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT; + private final Context mContext; + + private int mDefaultGravity; + private int mTvPipGravity; + private int mPreviousCollapsedGravity; + private boolean mIsRtl; private final boolean mIsTvExpandedPipSupported; private boolean mIsTvPipExpanded; private boolean mTvPipManuallyCollapsed; private float mDesiredTvExpandedAspectRatio; - private @Orientation int mTvFixedPipOrientation; - private int mTvPipGravity; - private @Nullable Size mTvExpandedSize; - private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE; - private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE; + @Orientation + private int mTvFixedPipOrientation; + @Nullable + private Size mTvExpandedSize; + @NonNull + private Insets mPipMenuPermanentDecorInsets = Insets.NONE; + @NonNull + private Insets mPipMenuTemporaryDecorInsets = Insets.NONE; public TvPipBoundsState(@NonNull Context context) { super(context); + mContext = context; + updateDefaultGravity(); + mPreviousCollapsedGravity = mDefaultGravity; mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE); } + public int getDefaultGravity() { + return mDefaultGravity; + } + + private void updateDefaultGravity() { + boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection() + == View.LAYOUT_DIRECTION_RTL; + mDefaultGravity = Gravity.BOTTOM | (isRtl ? Gravity.LEFT : Gravity.RIGHT); + + if (mIsRtl != isRtl) { + int prevGravityX = mPreviousCollapsedGravity & Gravity.HORIZONTAL_GRAVITY_MASK; + int prevGravityY = mPreviousCollapsedGravity & Gravity.VERTICAL_GRAVITY_MASK; + if ((prevGravityX & Gravity.RIGHT) == Gravity.RIGHT) { + mPreviousCollapsedGravity = Gravity.LEFT | prevGravityY; + } else if ((prevGravityX & Gravity.LEFT) == Gravity.LEFT) { + mPreviousCollapsedGravity = Gravity.RIGHT | prevGravityY; + } + } + mIsRtl = isRtl; + } + + @Override + public void onConfigurationChanged() { + updateDefaultGravity(); + } + /** * Initialize states when first entering PiP. */ @@ -86,7 +124,9 @@ public class TvPipBoundsState extends PipBoundsState { /** Resets the TV PiP state for a new activity. */ public void resetTvPipState() { mTvFixedPipOrientation = ORIENTATION_UNDETERMINED; - mTvPipGravity = DEFAULT_TV_GRAVITY; + mTvPipGravity = mDefaultGravity; + mPreviousCollapsedGravity = mDefaultGravity; + mTvPipManuallyCollapsed = false; } /** Set the tv expanded bounds of PiP */ @@ -101,16 +141,23 @@ public class TvPipBoundsState extends PipBoundsState { } /** Set the PiP aspect ratio for the expanded PiP (TV) that is desired by the app. */ - public void setDesiredTvExpandedAspectRatio(float aspectRatio, boolean override) { + public void setDesiredTvExpandedAspectRatio(float expandedAspectRatio, boolean override) { if (override || mTvFixedPipOrientation == ORIENTATION_UNDETERMINED) { - mDesiredTvExpandedAspectRatio = aspectRatio; resetTvPipState(); + mDesiredTvExpandedAspectRatio = expandedAspectRatio; + if (expandedAspectRatio != 0) { + if (expandedAspectRatio > 1) { + mTvFixedPipOrientation = ORIENTATION_HORIZONTAL; + } else { + mTvFixedPipOrientation = ORIENTATION_VERTICAL; + } + } return; } - if ((aspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL) - || (aspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL) - || aspectRatio == 0) { - mDesiredTvExpandedAspectRatio = aspectRatio; + if ((expandedAspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL) + || (expandedAspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL) + || expandedAspectRatio == 0) { + mDesiredTvExpandedAspectRatio = expandedAspectRatio; } } @@ -122,11 +169,6 @@ public class TvPipBoundsState extends PipBoundsState { return mDesiredTvExpandedAspectRatio; } - /** Sets the orientation the expanded TV PiP activity has been fixed to. */ - public void setTvFixedPipOrientation(@Orientation int orientation) { - mTvFixedPipOrientation = orientation; - } - /** Returns the fixed orientation of the expanded PiP on TV. */ @Orientation public int getTvFixedPipOrientation() { @@ -143,6 +185,14 @@ public class TvPipBoundsState extends PipBoundsState { return mTvPipGravity; } + public void setTvPipPreviousCollapsedGravity(int gravity) { + mPreviousCollapsedGravity = gravity; + } + + public int getTvPipPreviousCollapsedGravity() { + return mPreviousCollapsedGravity; + } + /** Sets whether the TV PiP is currently expanded. */ public void setTvPipExpanded(boolean expanded) { mIsTvPipExpanded = expanded; @@ -172,7 +222,8 @@ public class TvPipBoundsState extends PipBoundsState { mPipMenuPermanentDecorInsets = permanentInsets; } - public @NonNull Insets getPipMenuPermanentDecorInsets() { + @NonNull + public Insets getPipMenuPermanentDecorInsets() { return mPipMenuPermanentDecorInsets; } @@ -180,7 +231,8 @@ public class TvPipBoundsState extends PipBoundsState { mPipMenuTemporaryDecorInsets = temporaryDecorInsets; } - public @NonNull Insets getPipMenuTemporaryDecorInsets() { + @NonNull + public Insets getPipMenuTemporaryDecorInsets() { return mPipMenuTemporaryDecorInsets; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 4e1b0469eb96..fd4fcff54f01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -18,17 +18,22 @@ package com.android.wm.shell.pip.tv; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.KeyEvent.KEYCODE_DPAD_LEFT; +import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT; import android.annotation.IntDef; import android.app.ActivityManager; import android.app.ActivityTaskManager; -import android.app.PendingIntent; import android.app.RemoteAction; import android.app.TaskInfo; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; +import android.os.Handler; import android.os.RemoteException; import android.view.Gravity; @@ -67,10 +72,9 @@ import java.util.Set; */ public class TvPipController implements PipTransitionController.PipTransitionCallback, TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate, - TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener, - ConfigurationChangeListener, UserChangeListener { + DisplayController.OnDisplaysChangedListener, ConfigurationChangeListener, + UserChangeListener { private static final String TAG = "TvPipController"; - static final boolean DEBUG = false; private static final int NONEXISTENT_TASK_ID = -1; @@ -80,7 +84,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal STATE_PIP, STATE_PIP_MENU, }) - public @interface State {} + public @interface State { + } /** * State when there is no applications in Pip. @@ -98,6 +103,17 @@ public class TvPipController implements PipTransitionController.PipTransitionCal */ private static final int STATE_PIP_MENU = 2; + static final String ACTION_SHOW_PIP_MENU = + "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU"; + static final String ACTION_CLOSE_PIP = + "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP"; + static final String ACTION_MOVE_PIP = + "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP"; + static final String ACTION_TOGGLE_EXPANDED_PIP = + "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP"; + static final String ACTION_TO_FULLSCREEN = + "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN"; + private final Context mContext; private final ShellController mShellController; @@ -107,6 +123,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final PipAppOpsListener mAppOpsListener; private final PipTaskOrganizer mPipTaskOrganizer; private final PipMediaController mPipMediaController; + private final TvPipActionsProvider mTvPipActionsProvider; private final TvPipNotificationController mPipNotificationController; private final TvPipMenuController mTvPipMenuController; private final PipTransitionController mPipTransitionController; @@ -115,18 +132,20 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final DisplayController mDisplayController; private final WindowManagerShellWrapper mWmShellWrapper; private final ShellExecutor mMainExecutor; + private final Handler mMainHandler; // For registering the broadcast receiver private final TvPipImpl mImpl = new TvPipImpl(); - private @State int mState = STATE_NO_PIP; - private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY; + private final ActionBroadcastReceiver mActionBroadcastReceiver; + + @State + private int mState = STATE_NO_PIP; private int mPinnedTaskId = NONEXISTENT_TASK_ID; - private RemoteAction mCloseAction; // How long the shell will wait for the app to close the PiP if a custom action is set. private int mPipForceCloseDelay; private int mResizeAnimationDuration; - private int mEduTextWindowExitAnimationDurationMs; + private int mEduTextWindowExitAnimationDuration; public static Pip create( Context context, @@ -145,6 +164,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper wmShell, + Handler mainHandler, ShellExecutor mainExecutor) { return new TvPipController( context, @@ -163,6 +183,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal pipParamsChangedForwarder, displayController, wmShell, + mainHandler, mainExecutor).mImpl; } @@ -183,8 +204,10 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper wmShellWrapper, + Handler mainHandler, ShellExecutor mainExecutor) { mContext = context; + mMainHandler = mainHandler; mMainExecutor = mainExecutor; mShellController = shellController; mDisplayController = displayController; @@ -192,17 +215,23 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mTvPipBoundsState = tvPipBoundsState; mTvPipBoundsState.setDisplayId(context.getDisplayId()); mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay())); + mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm; mTvPipBoundsController = tvPipBoundsController; mTvPipBoundsController.setListener(this); mPipMediaController = pipMediaController; + mTvPipActionsProvider = new TvPipActionsProvider(context, pipMediaController, + this::executeAction); mPipNotificationController = pipNotificationController; - mPipNotificationController.setDelegate(this); + mPipNotificationController.setTvPipActionsProvider(mTvPipActionsProvider); mTvPipMenuController = tvPipMenuController; mTvPipMenuController.setDelegate(this); + mTvPipMenuController.setTvPipActionsProvider(mTvPipActionsProvider); + + mActionBroadcastReceiver = new ActionBroadcastReceiver(); mAppOpsListener = pipAppOpsListener; mPipTaskOrganizer = pipTaskOrganizer; @@ -216,7 +245,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private void onInit() { mPipTransitionController.registerPipTransitionCallback(this); - loadConfigurations(); + reloadResources(); registerPipParamsChangedListener(mPipParamsChangedForwarder); registerTaskStackListenerCallback(mTaskStackListener); @@ -236,22 +265,41 @@ public class TvPipController implements PipTransitionController.PipTransitionCal @Override public void onConfigurationChanged(Configuration newConfig) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState)); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState)); - if (isPipShown()) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: > closing Pip.", TAG); - } - closePip(); - } + int previousDefaultGravityX = mTvPipBoundsState.getDefaultGravity() + & Gravity.HORIZONTAL_GRAVITY_MASK; - loadConfigurations(); - mPipNotificationController.onConfigurationChanged(mContext); + reloadResources(); + + mPipNotificationController.onConfigurationChanged(); mTvPipBoundsAlgorithm.onConfigurationChanged(mContext); + mTvPipBoundsState.onConfigurationChanged(); + + int defaultGravityX = mTvPipBoundsState.getDefaultGravity() + & Gravity.HORIZONTAL_GRAVITY_MASK; + if (isPipShown() && previousDefaultGravityX != defaultGravityX) { + movePipToOppositeSide(); + } + } + + private void reloadResources() { + final Resources res = mContext.getResources(); + mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration); + mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay); + mEduTextWindowExitAnimationDuration = + res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration); + } + + private void movePipToOppositeSide() { + ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: movePipToOppositeSide", TAG); + if ((mTvPipBoundsState.getTvPipGravity() & Gravity.RIGHT) == Gravity.RIGHT) { + movePip(KEYCODE_DPAD_LEFT); + } else if ((mTvPipBoundsState.getTvPipGravity() & Gravity.LEFT) == Gravity.LEFT) { + movePip(KEYCODE_DPAD_RIGHT); + } } /** @@ -265,33 +313,32 @@ public class TvPipController implements PipTransitionController.PipTransitionCal * Starts the process if bringing up the Pip menu if by issuing a command to move Pip * task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip * task/window is properly positioned in {@link #onPipTransitionFinished(int)}. + * + * @param moveMenu If true, show the moveMenu, otherwise show the regular menu. */ - @Override - public void showPictureInPictureMenu() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: showPictureInPictureMenu(), state=%s", TAG, stateToName(mState)); - } + private void showPictureInPictureMenu(boolean moveMenu) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: showPictureInPictureMenu(), state=%s", TAG, stateToName(mState)); if (mState == STATE_NO_PIP) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: > cannot open Menu from the current state.", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: > cannot open Menu from the current state.", TAG); return; } setState(STATE_PIP_MENU); - mTvPipMenuController.showMenu(); + if (moveMenu) { + mTvPipMenuController.showMovementMenu(); + } else { + mTvPipMenuController.showMenu(); + } updatePinnedStackBounds(); } @Override public void onMenuClosed() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: closeMenu(), state before=%s", TAG, stateToName(mState)); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: closeMenu(), state before=%s", TAG, stateToName(mState)); setState(STATE_PIP); updatePinnedStackBounds(); } @@ -304,66 +351,37 @@ public class TvPipController implements PipTransitionController.PipTransitionCal /** * Opens the "Pip-ed" Activity fullscreen. */ - @Override - public void movePipToFullscreen() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState)); - } + private void movePipToFullscreen() { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState)); mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */); onPipDisappeared(); } - @Override - public void togglePipExpansion() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: togglePipExpansion()", TAG); - } + private void togglePipExpansion() { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: togglePipExpansion()", TAG); boolean expanding = !mTvPipBoundsState.isTvPipExpanded(); - int saveGravity = mTvPipBoundsAlgorithm - .updateGravityOnExpandToggled(mPreviousGravity, expanding); - if (saveGravity != Gravity.NO_GRAVITY) { - mPreviousGravity = saveGravity; - } + mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(expanding); mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding); mTvPipBoundsState.setTvPipExpanded(expanding); - mPipNotificationController.updateExpansionState(); updatePinnedStackBounds(); } @Override - public void enterPipMovementMenu() { - setState(STATE_PIP_MENU); - mTvPipMenuController.showMovementMenuOnly(); - } - - @Override public void movePip(int keycode) { if (mTvPipBoundsAlgorithm.updateGravity(keycode)) { mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity()); - mPreviousGravity = Gravity.NO_GRAVITY; updatePinnedStackBounds(); } else { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Position hasn't changed", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Position hasn't changed", TAG); } } @Override - public int getPipGravity() { - return mTvPipBoundsState.getTvPipGravity(); - } - - public int getOrientation() { - return mTvPipBoundsState.getTvFixedPipOrientation(); - } - - @Override public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted, Set<Rect> unrestricted) { if (mTvPipBoundsState.getDisplayId() == displayId) { @@ -392,34 +410,25 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } @Override - public void onPipTargetBoundsChange(Rect newTargetBounds, int animationDuration) { - mPipTaskOrganizer.scheduleAnimateResizePip(newTargetBounds, - animationDuration, rect -> mTvPipMenuController.updateExpansionState()); - mTvPipMenuController.onPipTransitionStarted(newTargetBounds); + public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) { + mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds, + animationDuration, null); + mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds); } /** * Closes Pip window. */ - @Override public void closePip() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: closePip(), state=%s, loseAction=%s", TAG, stateToName(mState), - mCloseAction); - } + closeCurrentPiP(mPinnedTaskId); + } - if (mCloseAction != null) { - try { - mCloseAction.getActionIntent().send(); - } catch (PendingIntent.CanceledException e) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Failed to send close action, %s", TAG, e); - } - mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay); - } else { - closeCurrentPiP(mPinnedTaskId); - } + /** + * Force close the current PiP after some time in case the custom action hasn't done it by + * itself. + */ + public void customClosePip() { + mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay); } private void closeCurrentPiP(int pinnedTaskId) { @@ -434,7 +443,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal @Override public void closeEduText() { - updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs, false); + updatePinnedStackBounds(mEduTextWindowExitAnimationDuration, false); } private void registerSessionListenerForCurrentUser() { @@ -443,22 +452,19 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private void checkIfPinnedTaskAppeared() { final TaskInfo pinnedTask = getPinnedTaskInfo(); - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: checkIfPinnedTaskAppeared(), task=%s", TAG, pinnedTask); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: checkIfPinnedTaskAppeared(), task=%s", TAG, pinnedTask); if (pinnedTask == null || pinnedTask.topActivity == null) return; mPinnedTaskId = pinnedTask.taskId; mPipMediaController.onActivityPinned(); + mActionBroadcastReceiver.register(); mPipNotificationController.show(pinnedTask.topActivity.getPackageName()); } private void checkIfPinnedTaskIsGone() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onTaskStackChanged()", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onTaskStackChanged()", TAG); if (isPipShown() && getPinnedTaskInfo() == null) { ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, @@ -468,12 +474,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } private void onPipDisappeared() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onPipDisappeared() state=%s", TAG, stateToName(mState)); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onPipDisappeared() state=%s", TAG, stateToName(mState)); mPipNotificationController.dismiss(); + mActionBroadcastReceiver.unregister(); + mTvPipMenuController.closeMenu(); mTvPipBoundsState.resetTvPipState(); mTvPipBoundsController.onPipDismissed(); @@ -482,50 +488,49 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } @Override - public void onPipTransitionStarted(int direction, Rect pipBounds) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onPipTransition_Started(), state=%s", TAG, stateToName(mState)); + public void onPipTransitionStarted(int direction, Rect currentPipBounds) { + final boolean enterPipTransition = PipAnimationController.isInPipDirection(direction); + if (enterPipTransition && mState == STATE_NO_PIP) { + // Set the initial ability to expand the PiP when entering PiP. + updateExpansionState(); } - mTvPipMenuController.notifyPipAnimating(true); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onPipTransition_Started(), state=%s, direction=%d", + TAG, stateToName(mState), direction); } @Override public void onPipTransitionCanceled(int direction) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState)); - } - mTvPipMenuController.notifyPipAnimating(false); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState)); + mTvPipMenuController.onPipTransitionFinished( + PipAnimationController.isInPipDirection(direction)); + mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded()); } @Override public void onPipTransitionFinished(int direction) { - if (PipAnimationController.isInPipDirection(direction) && mState == STATE_NO_PIP) { + final boolean enterPipTransition = PipAnimationController.isInPipDirection(direction); + if (enterPipTransition && mState == STATE_NO_PIP) { setState(STATE_PIP); } - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onPipTransition_Finished(), state=%s", TAG, stateToName(mState)); - } - mTvPipMenuController.notifyPipAnimating(false); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onPipTransition_Finished(), state=%s, direction=%d", + TAG, stateToName(mState), direction); + mTvPipMenuController.onPipTransitionFinished(enterPipTransition); + mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded()); } - private void setState(@State int state) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: setState(), state=%s, prev=%s", - TAG, stateToName(state), stateToName(mState)); - } - mState = state; + private void updateExpansionState() { + mTvPipActionsProvider.updateExpansionEnabled(mTvPipBoundsState.isTvExpandedPipSupported() + && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0); } - private void loadConfigurations() { - final Resources res = mContext.getResources(); - mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration); - mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay); - mEduTextWindowExitAnimationDurationMs = - res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms); + private void setState(@State int state) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: setState(), state=%s, prev=%s", + TAG, stateToName(state), stateToName(mState)); + mState = state; } private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) { @@ -550,11 +555,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { if (task.getWindowingMode() == WINDOWING_MODE_PINNED) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onPinnedActivityRestartAttempt()", TAG); - } - + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onPinnedActivityRestartAttempt()", TAG); // If the "Pip-ed" Activity is launched again by Launcher or intent, make it // fullscreen. movePipToFullscreen(); @@ -569,16 +571,15 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onActionsChanged()", TAG); + "%s: onActionsChanged()", TAG); - mTvPipMenuController.setAppActions(actions, closeAction); - mCloseAction = closeAction; + mTvPipActionsProvider.setAppActions(actions, closeAction); } @Override public void onAspectRatioChanged(float ratio) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onAspectRatioChanged: %f", TAG, ratio); + "%s: onAspectRatioChanged: %f", TAG, ratio); mTvPipBoundsState.setAspectRatio(ratio); if (!mTvPipBoundsState.isTvPipExpanded()) { @@ -589,10 +590,10 @@ public class TvPipController implements PipTransitionController.PipTransitionCal @Override public void onExpandedAspectRatioChanged(float ratio) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onExpandedAspectRatioChanged: %f", TAG, ratio); + "%s: onExpandedAspectRatioChanged: %f", TAG, ratio); mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false); - mTvPipMenuController.updateExpansionState(); + updateExpansionState(); // 1) PiP is expanded and only aspect ratio changed, but wasn't disabled // --> update bounds, but don't toggle @@ -604,11 +605,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal // 2) PiP is expanded, but expanded PiP was disabled // --> collapse PiP if (mTvPipBoundsState.isTvPipExpanded() && ratio == 0) { - int saveGravity = mTvPipBoundsAlgorithm - .updateGravityOnExpandToggled(mPreviousGravity, false); - if (saveGravity != Gravity.NO_GRAVITY) { - mPreviousGravity = saveGravity; - } + mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(/* expanding= */ false); mTvPipBoundsState.setTvPipExpanded(false); updatePinnedStackBounds(); } @@ -618,11 +615,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal if (!mTvPipBoundsState.isTvPipExpanded() && ratio != 0 && !mTvPipBoundsState.isTvPipManuallyCollapsed()) { mTvPipBoundsAlgorithm.updateExpandedPipSize(); - int saveGravity = mTvPipBoundsAlgorithm - .updateGravityOnExpandToggled(mPreviousGravity, true); - if (saveGravity != Gravity.NO_GRAVITY) { - mPreviousGravity = saveGravity; - } + mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(/* expanding= */ true); mTvPipBoundsState.setTvPipExpanded(true); updatePinnedStackBounds(); } @@ -635,11 +628,9 @@ public class TvPipController implements PipTransitionController.PipTransitionCal wmShell.addPinnedStackListener(new PinnedStackListenerForwarder.PinnedTaskListener() { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onImeVisibilityChanged(), visible=%b, height=%d", - TAG, imeVisible, imeHeight); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onImeVisibilityChanged(), visible=%b, height=%d", + TAG, imeVisible, imeHeight); if (imeVisible == mTvPipBoundsState.isImeShowing() && (!imeVisible || imeHeight == mTvPipBoundsState.getImeHeight())) { @@ -661,17 +652,13 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } private static TaskInfo getPinnedTaskInfo() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: getPinnedTaskInfo()", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: getPinnedTaskInfo()", TAG); try { final TaskInfo taskInfo = ActivityTaskManager.getService().getRootTaskInfo( WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: taskInfo=%s", TAG, taskInfo); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: taskInfo=%s", TAG, taskInfo); return taskInfo; } catch (RemoteException e) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, @@ -681,10 +668,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } private static void removeTask(int taskId) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: removeTask(), taskId=%d", TAG, taskId); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: removeTask(), taskId=%d", TAG, taskId); try { ActivityTaskManager.getService().removeTask(taskId); } catch (Exception e) { @@ -707,6 +692,90 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } } + private void executeAction(@TvPipAction.ActionType int actionType) { + switch (actionType) { + case TvPipAction.ACTION_FULLSCREEN: + movePipToFullscreen(); + break; + case TvPipAction.ACTION_CLOSE: + closePip(); + break; + case TvPipAction.ACTION_MOVE: + showPictureInPictureMenu(/* moveMenu= */ true); + break; + case TvPipAction.ACTION_CUSTOM_CLOSE: + customClosePip(); + break; + case TvPipAction.ACTION_EXPAND_COLLAPSE: + togglePipExpansion(); + break; + default: + // NOOP + break; + } + } + + private class ActionBroadcastReceiver extends BroadcastReceiver { + private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"; + + final IntentFilter mIntentFilter; + + { + mIntentFilter = new IntentFilter(); + mIntentFilter.addAction(ACTION_CLOSE_PIP); + mIntentFilter.addAction(ACTION_SHOW_PIP_MENU); + mIntentFilter.addAction(ACTION_MOVE_PIP); + mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP); + mIntentFilter.addAction(ACTION_TO_FULLSCREEN); + } + + boolean mRegistered = false; + + void register() { + if (mRegistered) return; + + mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION, + mMainHandler); + mRegistered = true; + } + + void unregister() { + if (!mRegistered) return; + + mContext.unregisterReceiver(this); + mRegistered = false; + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: on(Broadcast)Receive(), action=%s", TAG, action); + + if (ACTION_SHOW_PIP_MENU.equals(action)) { + showPictureInPictureMenu(/* moveMenu= */ false); + } else { + executeAction(getCorrespondingActionType(action)); + } + } + + @TvPipAction.ActionType + private int getCorrespondingActionType(String broadcast) { + if (ACTION_CLOSE_PIP.equals(broadcast)) { + return TvPipAction.ACTION_CLOSE; + } else if (ACTION_MOVE_PIP.equals(broadcast)) { + return TvPipAction.ACTION_MOVE; + } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(broadcast)) { + return TvPipAction.ACTION_EXPAND_COLLAPSE; + } else if (ACTION_TO_FULLSCREEN.equals(broadcast)) { + return TvPipAction.ACTION_FULLSCREEN; + } + + // Default: handle it like an action we don't know the content of. + return TvPipAction.ACTION_CUSTOM; + } + } + private class TvPipImpl implements Pip { // Not used } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java new file mode 100644 index 000000000000..449a2bf09881 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 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.pip.tv; + +import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE; +import static android.app.Notification.Action.SEMANTIC_ACTION_NONE; + +import android.annotation.NonNull; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.TvWindowMenuActionButton; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +import java.util.List; +import java.util.Objects; + +/** + * A TvPipAction for actions that the app provides via {@link + * android.app.PictureInPictureParams.Builder#setCloseAction(RemoteAction)} or {@link + * android.app.PictureInPictureParams.Builder#setActions(List)}. + */ +public class TvPipCustomAction extends TvPipAction { + private static final String TAG = TvPipCustomAction.class.getSimpleName(); + + private final RemoteAction mRemoteAction; + + TvPipCustomAction(@ActionType int actionType, @NonNull RemoteAction remoteAction, + SystemActionsHandler systemActionsHandler) { + super(actionType, systemActionsHandler); + Objects.requireNonNull(remoteAction); + mRemoteAction = remoteAction; + } + + void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) { + if (button == null || mainHandler == null) return; + if (mRemoteAction.getContentDescription().length() > 0) { + button.setTextAndDescription(mRemoteAction.getContentDescription()); + } else { + button.setTextAndDescription(mRemoteAction.getTitle()); + } + button.setImageIconAsync(mRemoteAction.getIcon(), mainHandler); + button.setEnabled(isCloseAction() || mRemoteAction.isEnabled()); + } + + PendingIntent getPendingIntent() { + return mRemoteAction.getActionIntent(); + } + + void executeAction() { + super.executeAction(); + try { + mRemoteAction.getActionIntent().send(); + } catch (PendingIntent.CanceledException e) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Failed to send action, %s", TAG, e); + } + } + + @Override + Notification.Action toNotificationAction(Context context) { + Notification.Action.Builder builder = new Notification.Action.Builder( + mRemoteAction.getIcon(), + mRemoteAction.getTitle(), + mRemoteAction.getActionIntent()); + Bundle extras = new Bundle(); + extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION, + mRemoteAction.getContentDescription()); + builder.addExtras(extras); + + builder.setSemanticAction(isCloseAction() + ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE); + builder.setContextual(true); + return builder.build(); + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt index 1e54436ebce9..a94bd6ec1040 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt @@ -284,8 +284,10 @@ class TvPipKeepClearAlgorithm() { ): Rect? { val movementBounds = transformedMovementBounds val candidateEdgeRects = mutableListOf<Rect>() + val maxRestrictedXDistanceFraction = + if (isPipAnchoredToCorner()) maxRestrictedDistanceFraction else 0.0 val minRestrictedLeft = - pipAnchorBounds.right - screenSize.width * maxRestrictedDistanceFraction + pipAnchorBounds.right - screenSize.width * maxRestrictedXDistanceFraction candidateEdgeRects.add( movementBounds.offsetCopy(movementBounds.width() + pipAreaPadding, 0) @@ -296,7 +298,6 @@ class TvPipKeepClearAlgorithm() { // throw out edges that are too close to the left screen edge to fit the PiP val minLeft = movementBounds.left + pipAnchorBounds.width() candidateEdgeRects.retainAll { it.left - pipAreaPadding > minLeft } - candidateEdgeRects.sortBy { -it.left } val maxRestrictedDY = (screenSize.height * maxRestrictedDistanceFraction).roundToInt() @@ -335,8 +336,7 @@ class TvPipKeepClearAlgorithm() { } } - candidateBounds.sortBy { candidateCost(it, pipAnchorBounds) } - return candidateBounds.firstOrNull() + return candidateBounds.minByOrNull { candidateCost(it, pipAnchorBounds) } } private fun getNearbyStashedPosition(bounds: Rect, keepClearAreas: Set<Rect>): Rect { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 4ce45e142c64..8895fcab0c3d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -41,56 +41,44 @@ import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.SystemWindows; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import java.util.ArrayList; import java.util.List; -import java.util.Objects; /** * Manages the visibility of the PiP Menu as user interacts with PiP. */ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener { private static final String TAG = "TvPipMenuController"; - private static final boolean DEBUG = TvPipController.DEBUG; private static final String BACKGROUND_WINDOW_TITLE = "PipBackgroundView"; private final Context mContext; private final SystemWindows mSystemWindows; private final TvPipBoundsState mTvPipBoundsState; private final Handler mMainHandler; - private final int mPipMenuBorderWidth; - private final int mPipEduTextShowDurationMs; - private final int mPipEduTextHeight; + private TvPipActionsProvider mTvPipActionsProvider; private Delegate mDelegate; private SurfaceControl mLeash; private TvPipMenuView mPipMenuView; private View mPipBackgroundView; + private boolean mMenuIsOpen; // User can actively move the PiP via the DPAD. private boolean mInMoveMode; // Used when only showing the move menu since we want to close the menu completely when // exiting the move menu instead of showing the regular button menu. private boolean mCloseAfterExitMoveMenu; - private final List<RemoteAction> mMediaActions = new ArrayList<>(); - private final List<RemoteAction> mAppActions = new ArrayList<>(); - private RemoteAction mCloseAction; - private SyncRtSurfaceTransactionApplier mApplier; private SyncRtSurfaceTransactionApplier mBackgroundApplier; RectF mTmpSourceRectF = new RectF(); RectF mTmpDestinationRectF = new RectF(); Matrix mMoveTransform = new Matrix(); - private final Runnable mCloseEduTextRunnable = this::closeEduText; - public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState, - SystemWindows systemWindows, PipMediaController pipMediaController, - Handler mainHandler) { + SystemWindows systemWindows, Handler mainHandler) { mContext = context; mTvPipBoundsState = tvPipBoundsState; mSystemWindows = systemWindows; @@ -107,22 +95,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */, mainHandler, Context.RECEIVER_EXPORTED); - - pipMediaController.addActionListener(this::onMediaActionsChanged); - - mPipEduTextShowDurationMs = context.getResources() - .getInteger(R.integer.pip_edu_text_show_duration_ms); - mPipEduTextHeight = context.getResources() - .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height); - mPipMenuBorderWidth = context.getResources() - .getDimensionPixelSize(R.dimen.pip_menu_border_width); } void setDelegate(Delegate delegate) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: setDelegate(), delegate=%s", TAG, delegate); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: setDelegate(), delegate=%s", TAG, delegate); if (mDelegate != null) { throw new IllegalStateException( "The delegate has already been set and should not change."); @@ -134,6 +111,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis mDelegate = delegate; } + void setTvPipActionsProvider(TvPipActionsProvider tvPipActionsProvider) { + mTvPipActionsProvider = tvPipActionsProvider; + } + @Override public void attach(SurfaceControl leash) { if (mDelegate == null) { @@ -145,10 +126,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } private void attachPipMenu() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: attachPipMenu()", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: attachPipMenu()", TAG); if (mPipMenuView != null) { detachPipMenu(); @@ -157,18 +136,24 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis attachPipBackgroundView(); attachPipMenuView(); - mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-mPipMenuBorderWidth, - -mPipMenuBorderWidth, -mPipMenuBorderWidth, -mPipMenuBorderWidth)); - mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -mPipEduTextHeight)); - mMainHandler.postDelayed(mCloseEduTextRunnable, mPipEduTextShowDurationMs); + int pipEduTextHeight = mContext.getResources() + .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height); + int pipMenuBorderWidth = mContext.getResources() + .getDimensionPixelSize(R.dimen.pip_menu_border_width); + mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipMenuBorderWidth, + -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth)); + mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight)); } private void attachPipMenuView() { - mPipMenuView = new TvPipMenuView(mContext); - mPipMenuView.setListener(this); + if (mTvPipActionsProvider == null) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Actions provider is not set", TAG); + return; + } + mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider); setUpViewSurfaceZOrder(mPipMenuView, 1); addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE); - maybeUpdateMenuViewActions(); } private void attachPipBackgroundView() { @@ -180,45 +165,50 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis private void setUpViewSurfaceZOrder(View v, int zOrderRelativeToPip) { v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - v.getViewRootImpl().addSurfaceChangedCallback( - new PipMenuSurfaceChangedCallback(v, zOrderRelativeToPip)); - } - - @Override - public void onViewDetachedFromWindow(View v) { - } + @Override + public void onViewAttachedToWindow(View v) { + v.getViewRootImpl().addSurfaceChangedCallback( + new PipMenuSurfaceChangedCallback(v, zOrderRelativeToPip)); + } + + @Override + public void onViewDetachedFromWindow(View v) { + } }); } private void addPipMenuViewToSystemWindows(View v, String title) { - mSystemWindows.addView(v, getPipMenuLayoutParams(title, 0 /* width */, 0 /* height */), - 0 /* displayId */, SHELL_ROOT_LAYER_PIP); - } - - void notifyPipAnimating(boolean animating) { - mPipMenuView.setEduTextActive(!animating); - if (!animating) { - mPipMenuView.onPipTransitionFinished(); - } + mSystemWindows.addView(v, getPipMenuLayoutParams(mContext, title, 0 /* width */, + 0 /* height */), 0 /* displayId */, SHELL_ROOT_LAYER_PIP); + } + + void onPipTransitionFinished(boolean enterTransition) { + // There is a race between when this is called and when the last frame of the pip transition + // is drawn. To ensure that view updates are applied only when the animation has fully drawn + // and the menu view has been fully remeasured and relaid out, we add a small delay here by + // posting on the handler. + mMainHandler.post(() -> { + if (mPipMenuView != null) { + mPipMenuView.onPipTransitionFinished(enterTransition); + } + }); } - void showMovementMenuOnly() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: showMovementMenuOnly()", TAG); - } + void showMovementMenu() { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: showMovementMenuOnly()", TAG); setInMoveMode(true); - mCloseAfterExitMoveMenu = true; - showMenuInternal(); + if (mMenuIsOpen) { + mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity()); + } else { + mCloseAfterExitMoveMenu = true; + showMenuInternal(); + } } @Override public void showMenu() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG); setInMoveMode(false); mCloseAfterExitMoveMenu = false; showMenuInternal(); @@ -228,46 +218,27 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis if (mPipMenuView == null) { return; } - maybeCloseEduText(); - maybeUpdateMenuViewActions(); - updateExpansionState(); + mMenuIsOpen = true; grantPipMenuFocus(true); if (mInMoveMode) { - mPipMenuView.showMoveMenu(mDelegate.getPipGravity()); + mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity()); } else { - mPipMenuView.showButtonsMenu(); + mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false); } mPipMenuView.updateBounds(mTvPipBoundsState.getBounds()); } - void onPipTransitionStarted(Rect finishBounds) { + void onPipTransitionToTargetBoundsStarted(Rect targetBounds) { if (mPipMenuView != null) { - mPipMenuView.onPipTransitionStarted(finishBounds); + mPipMenuView.onPipTransitionToTargetBoundsStarted(targetBounds); } } - private void maybeCloseEduText() { - if (mMainHandler.hasCallbacks(mCloseEduTextRunnable)) { - mMainHandler.removeCallbacks(mCloseEduTextRunnable); - mCloseEduTextRunnable.run(); - } - } - - private void closeEduText() { - mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE); - mPipMenuView.hideEduText(); - mDelegate.closeEduText(); - } - void updateGravity(int gravity) { - mPipMenuView.showMovementHints(gravity); - } - - void updateExpansionState() { - mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipSupported() - && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0); - mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded()); + if (mInMoveMode) { + mPipMenuView.showMovementHints(gravity); + } } private Rect calculateMenuSurfaceBounds(Rect pipBounds) { @@ -275,15 +246,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } void closeMenu() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: closeMenu()", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: closeMenu()", TAG); if (mPipMenuView == null) { return; } + mMenuIsOpen = false; mPipMenuView.hideAllUserControls(); grantPipMenuFocus(false); mDelegate.onMenuClosed(); @@ -297,7 +267,6 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis if (mInMoveMode == moveMode) { return; } - mInMoveMode = moveMode; if (mDelegate != null) { mDelegate.onInMoveModeChanged(); @@ -305,32 +274,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } @Override - public void onEnterMoveMode() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode, - mCloseAfterExitMoveMenu); - } - setInMoveMode(true); - mPipMenuView.showMoveMenu(mDelegate.getPipGravity()); - } - - @Override public boolean onExitMoveMode() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onExitMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode, - mCloseAfterExitMoveMenu); - } - if (mCloseAfterExitMoveMenu) { - setInMoveMode(false); - mCloseAfterExitMoveMenu = false; - closeMenu(); - return true; - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onExitMoveMode - %b, close when exiting move menu: %b", + TAG, mInMoveMode, mCloseAfterExitMoveMenu); + if (mInMoveMode) { setInMoveMode(false); - mPipMenuView.showButtonsMenu(); + if (mCloseAfterExitMoveMenu) { + mCloseAfterExitMoveMenu = false; + closeMenu(); + } else { + mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true); + } return true; } return false; @@ -338,10 +294,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public boolean onPipMovement(int keycode) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onPipMovement - %b", TAG, mInMoveMode); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onPipMovement - %b", TAG, mInMoveMode); if (mInMoveMode) { mDelegate.movePip(keycode); } @@ -351,62 +305,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void detach() { closeMenu(); - mMainHandler.removeCallbacks(mCloseEduTextRunnable); detachPipMenu(); mLeash = null; } @Override public void setAppActions(List<RemoteAction> actions, RemoteAction closeAction) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: setAppActions()", TAG); - } - updateAdditionalActionsList(mAppActions, actions, closeAction); - } - - private void onMediaActionsChanged(List<RemoteAction> actions) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onMediaActionsChanged()", TAG); - } - - // Hide disabled actions. - List<RemoteAction> enabledActions = new ArrayList<>(); - for (RemoteAction remoteAction : actions) { - if (remoteAction.isEnabled()) { - enabledActions.add(remoteAction); - } - } - updateAdditionalActionsList(mMediaActions, enabledActions, mCloseAction); - } - - private void updateAdditionalActionsList(List<RemoteAction> destination, - @Nullable List<RemoteAction> source, RemoteAction closeAction) { - final int number = source != null ? source.size() : 0; - if (number == 0 && destination.isEmpty() && Objects.equals(closeAction, mCloseAction)) { - // Nothing changed. - return; - } - - mCloseAction = closeAction; - - destination.clear(); - if (number > 0) { - destination.addAll(source); - } - maybeUpdateMenuViewActions(); - } - - private void maybeUpdateMenuViewActions() { - if (mPipMenuView == null) { - return; - } - if (!mAppActions.isEmpty()) { - mPipMenuView.setAdditionalActions(mAppActions, mCloseAction, mMainHandler); - } else { - mPipMenuView.setAdditionalActions(mMediaActions, mCloseAction, mMainHandler); - } + // NOOP - handled via the TvPipActionsProvider } @Override @@ -421,10 +326,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis public void resizePipMenu(@Nullable SurfaceControl pipLeash, @Nullable SurfaceControl.Transaction t, Rect destinationBounds) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: resizePipMenu: %s", TAG, destinationBounds.toShortString()); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: resizePipMenu: %s", TAG, destinationBounds.toShortString()); if (destinationBounds.isEmpty()) { return; } @@ -438,14 +341,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView); final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface) - .withWindowCrop(menuBounds) - .build(); + .withWindowCrop(menuBounds) + .build(); final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView); final SyncRtSurfaceTransactionApplier.SurfaceParams backParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface) - .withWindowCrop(menuBounds) - .build(); + .withWindowCrop(menuBounds) + .build(); // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the // animations of the pip surface with the content of the front and back menu surfaces @@ -468,13 +371,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction, Rect pipDestBounds) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: movePipMenu: %s", TAG, pipDestBounds.toShortString()); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: movePipMenu: %s", TAG, pipDestBounds.toShortString()); if (pipDestBounds.isEmpty()) { - if (transaction == null && DEBUG) { + if (transaction == null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: no transaction given", TAG); } @@ -490,16 +391,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis // resizing and the PiP menu is also resized. We then want to do a scale from the current // new menu bounds. if (pipLeash != null && transaction != null) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: tmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: tmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG); mPipMenuView.getBoundsOnScreen(tmpSourceBounds); } else { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: tmpSourceBounds based on menu width and height", TAG); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: tmpSourceBounds based on menu width and height", TAG); tmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height()); } @@ -525,8 +422,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis if (pipLeash != null && transaction != null) { final SyncRtSurfaceTransactionApplier.SurfaceParams pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash) - .withMergeTransaction(transaction) - .build(); + .withMergeTransaction(transaction) + .build(); mApplier.scheduleApply(frontParams, pipParams); } else { mApplier.scheduleApply(frontParams); @@ -568,15 +465,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void updateMenuBounds(Rect destinationBounds) { final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds); - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString()); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString()); mSystemWindows.updateViewLayout(mPipBackgroundView, - getPipMenuLayoutParams(BACKGROUND_WINDOW_TITLE, menuBounds.width(), + getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(), menuBounds.height())); mSystemWindows.updateViewLayout(mPipMenuView, - getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(), + getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height())); if (mPipMenuView != null) { @@ -597,43 +492,24 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } @Override - public void onCloseButtonClick() { - mDelegate.closePip(); - } - - @Override - public void onFullscreenButtonClick() { - mDelegate.movePipToFullscreen(); - } - - @Override - public void onToggleExpandedMode() { - mDelegate.togglePipExpansion(); + public void onCloseEduText() { + mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE); + mDelegate.closeEduText(); } interface Delegate { - void movePipToFullscreen(); - void movePip(int keycode); void onInMoveModeChanged(); - int getPipGravity(); - - void togglePipExpansion(); - void onMenuClosed(); void closeEduText(); - - void closePip(); } private void grantPipMenuFocus(boolean grantFocus) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: grantWindowFocus(%b)", TAG, grantFocus); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: grantWindowFocus(%b)", TAG, grantFocus); try { WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java new file mode 100644 index 000000000000..6eef22562caa --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2022 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.pip.tv; + +import static android.view.Gravity.BOTTOM; +import static android.view.Gravity.CENTER; +import static android.view.View.GONE; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; + +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.text.Annotation; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannedString; +import android.text.TextUtils; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; + +import java.util.Arrays; + +/** + * The edu text drawer shows the user a hint for how to access the Picture-in-Picture menu. + * It displays a text in a drawer below the Picture-in-Picture window. The drawer has the same + * width as the Picture-in-Picture window. Depending on the Picture-in-Picture mode, there might + * not be enough space to fit the whole educational text in the available space. In such cases we + * apply a marquee animation to the TextView inside the drawer. + * + * The drawer is shown temporarily giving the user enough time to read it, after which it slides + * shut. We show the text for a duration calculated based on whether the text is marqueed or not. + */ +class TvPipMenuEduTextDrawer extends FrameLayout { + private static final String TAG = "TvPipMenuEduTextDrawer"; + + private static final float MARQUEE_DP_PER_SECOND = 30; // Copy of TextView.MARQUEE_DP_PER_SECOND + private static final int MARQUEE_RESTART_DELAY = 1200; // Copy of TextView.MARQUEE_DELAY + private final float mMarqueeAnimSpeed; // pixels per ms + + private final Runnable mCloseDrawerRunnable = this::closeDrawer; + private final Runnable mStartScrollEduTextRunnable = this::startScrollEduText; + + private final Handler mMainHandler; + private final Listener mListener; + private final TextView mEduTextView; + + TvPipMenuEduTextDrawer(@NonNull Context context, Handler mainHandler, Listener listener) { + super(context, null, 0, 0); + + mListener = listener; + mMainHandler = mainHandler; + + // Taken from TextView.Marquee calculation + mMarqueeAnimSpeed = + (MARQUEE_DP_PER_SECOND * context.getResources().getDisplayMetrics().density) / 1000f; + + mEduTextView = new TextView(mContext); + setupDrawer(); + } + + private void setupDrawer() { + final int eduTextHeight = mContext.getResources().getDimensionPixelSize( + R.dimen.pip_menu_edu_text_view_height); + final int marqueeRepeatLimit = mContext.getResources() + .getInteger(R.integer.pip_edu_text_scroll_times); + + mEduTextView.setLayoutParams( + new LayoutParams(MATCH_PARENT, eduTextHeight, BOTTOM | CENTER)); + mEduTextView.setGravity(CENTER); + mEduTextView.setClickable(false); + mEduTextView.setText(createEduTextString()); + mEduTextView.setSingleLine(); + mEduTextView.setTextAppearance(R.style.TvPipEduText); + mEduTextView.setEllipsize(TextUtils.TruncateAt.MARQUEE); + mEduTextView.setMarqueeRepeatLimit(marqueeRepeatLimit); + mEduTextView.setHorizontallyScrolling(true); + mEduTextView.setHorizontalFadingEdgeEnabled(true); + mEduTextView.setSelected(false); + addView(mEduTextView); + + setLayoutParams(new LayoutParams(MATCH_PARENT, eduTextHeight, CENTER)); + setClipChildren(true); + } + + /** + * Initializes the edu text. Should only be called once when the PiP is entered + */ + void init() { + ProtoLog.i(WM_SHELL_PICTURE_IN_PICTURE, "%s: init()", TAG); + scheduleLifecycleEvents(); + } + + private void scheduleLifecycleEvents() { + final int startScrollDelay = mContext.getResources().getInteger( + R.integer.pip_edu_text_start_scroll_delay); + if (isEduTextMarqueed()) { + mMainHandler.postDelayed(mStartScrollEduTextRunnable, startScrollDelay); + } + mMainHandler.postDelayed(mCloseDrawerRunnable, startScrollDelay + getEduTextShowDuration()); + mEduTextView.getViewTreeObserver().addOnWindowAttachListener( + new ViewTreeObserver.OnWindowAttachListener() { + @Override + public void onWindowAttached() { + } + + @Override + public void onWindowDetached() { + mEduTextView.getViewTreeObserver().removeOnWindowAttachListener(this); + mMainHandler.removeCallbacks(mStartScrollEduTextRunnable); + mMainHandler.removeCallbacks(mCloseDrawerRunnable); + } + }); + } + + private int getEduTextShowDuration() { + int eduTextShowDuration; + if (isEduTextMarqueed()) { + // Calculate the time it takes to fully scroll the text once: time = distance / speed + final float singleMarqueeDuration = + getMarqueeAnimEduTextLineWidth() / mMarqueeAnimSpeed; + // The TextView adds a delay between each marquee repetition. Take that into account + final float durationFromStartToStart = singleMarqueeDuration + MARQUEE_RESTART_DELAY; + // Finally, multiply by the number of times we repeat the marquee animation + eduTextShowDuration = + (int) durationFromStartToStart * mEduTextView.getMarqueeRepeatLimit(); + } else { + eduTextShowDuration = mContext.getResources() + .getInteger(R.integer.pip_edu_text_non_scroll_show_duration); + } + + ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: getEduTextShowDuration(), showDuration=%d", + TAG, eduTextShowDuration); + return eduTextShowDuration; + } + + /** + * Returns true if the edu text width is bigger than the width of the text view, which indicates + * that the edu text will be marqueed + */ + private boolean isEduTextMarqueed() { + final int availableWidth = (int) mEduTextView.getWidth() + - mEduTextView.getCompoundPaddingLeft() + - mEduTextView.getCompoundPaddingRight(); + return availableWidth < getEduTextWidth(); + } + + /** + * Returns the width of a single marquee repetition of the edu text in pixels. + * This is the width from the start of the edu text to the start of the next edu + * text when it is marqueed. + * + * This is calculated based on the TextView.Marquee#start calculations + */ + private float getMarqueeAnimEduTextLineWidth() { + // When the TextView has a marquee animation, it puts a gap between the text end and the + // start of the next edu text repetition. The space is equal to a third of the TextView + // width + final float gap = mEduTextView.getWidth() / 3.0f; + return getEduTextWidth() + gap; + } + + private void startScrollEduText() { + ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: startScrollEduText(), repeat=%d", + TAG, mEduTextView.getMarqueeRepeatLimit()); + mEduTextView.setSelected(true); + } + + /** + * Returns the width of the edu text irrespective of the TextView width + */ + private int getEduTextWidth() { + return (int) mEduTextView.getLayout().getLineWidth(0); + } + + /** + * Closes the edu text drawer if it hasn't been closed yet + */ + void closeIfNeeded() { + if (mMainHandler.hasCallbacks(mCloseDrawerRunnable)) { + ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, + "%s: close(), closing the edu text drawer because of user action", TAG); + mMainHandler.removeCallbacks(mCloseDrawerRunnable); + mCloseDrawerRunnable.run(); + } else { + // Do nothing, the drawer has already been closed + } + } + + private void closeDrawer() { + ProtoLog.i(WM_SHELL_PICTURE_IN_PICTURE, "%s: closeDrawer()", TAG); + final int eduTextFadeExitAnimationDuration = mContext.getResources().getInteger( + R.integer.pip_edu_text_view_exit_animation_duration); + final int eduTextSlideExitAnimationDuration = mContext.getResources().getInteger( + R.integer.pip_edu_text_window_exit_animation_duration); + + // Start fading out the edu text + mEduTextView.animate() + .alpha(0f) + .setInterpolator(TvPipInterpolators.EXIT) + .setDuration(eduTextFadeExitAnimationDuration) + .start(); + + // Start animation to close the drawer by animating its height to 0 + final ValueAnimator heightAnimation = ValueAnimator.ofInt(getHeight(), 0); + heightAnimation.setDuration(eduTextSlideExitAnimationDuration); + heightAnimation.setInterpolator(TvPipInterpolators.BROWSE); + heightAnimation.addUpdateListener(animator -> { + final ViewGroup.LayoutParams params = getLayoutParams(); + params.height = (int) animator.getAnimatedValue(); + setLayoutParams(params); + if (params.height == 0) { + setVisibility(GONE); + } + }); + heightAnimation.start(); + + mListener.onCloseEduText(); + } + + /** + * Creates the educational text that will be displayed to the user. Here we replace the + * HOME annotation in the String with an icon + */ + private CharSequence createEduTextString() { + final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text); + final SpannableString spannableString = new SpannableString(eduText); + Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst() + .ifPresent(annotation -> { + final Drawable icon = + getResources().getDrawable(R.drawable.home_icon, mContext.getTheme()); + if (icon != null) { + icon.mutate(); + icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + spannableString.setSpan(new CenteredImageSpan(icon), + eduText.getSpanStart(annotation), + eduText.getSpanEnd(annotation), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + }); + + return spannableString; + } + + /** + * A listener for edu text drawer event states. + */ + interface Listener { + /** + * The edu text closing impacts the size of the Picture-in-Picture window and influences + * how it is positioned on the screen. + */ + void onCloseEduText(); + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index 320c05c4a415..56c602a1d4f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -25,205 +25,135 @@ import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT; import static android.view.KeyEvent.KEYCODE_DPAD_UP; import static android.view.KeyEvent.KEYCODE_ENTER; -import android.animation.ValueAnimator; -import android.app.PendingIntent; -import android.app.RemoteAction; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE; + import android.content.Context; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.os.Handler; -import android.text.Annotation; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.SpannedString; -import android.util.AttributeSet; import android.view.Gravity; import android.view.KeyEvent; -import android.view.SurfaceControl; import android.view.View; import android.view.ViewGroup; -import android.view.ViewRootImpl; +import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; -import android.widget.HorizontalScrollView; import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.widget.LinearLayoutManager; +import com.android.internal.widget.RecyclerView; import com.android.wm.shell.R; +import com.android.wm.shell.common.TvWindowMenuActionButton; import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** - * A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu - * actions: Fullscreen, Move and Close, but could also display "additional" actions, that may be set - * via a {@link #setAdditionalActions(List, Handler)} call. + * A View that represents Pip Menu on TV. It's responsible for displaying the Pip menu actions from + * the TvPipActionsProvider as well as the buttons for manually moving the PiP. */ -public class TvPipMenuView extends FrameLayout implements View.OnClickListener { +public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.Listener { private static final String TAG = "TvPipMenuView"; - private static final boolean DEBUG = TvPipController.DEBUG; - private static final int FIRST_CUSTOM_ACTION_POSITION = 3; + private final TvPipMenuView.Listener mListener; - @Nullable - private Listener mListener; + private final TvPipActionsProvider mTvPipActionsProvider; + + private final RecyclerView mActionButtonsRecyclerView; + private final LinearLayoutManager mButtonLayoutManager; + private final RecyclerViewAdapter mRecyclerViewAdapter; - private final LinearLayout mActionButtonsContainer; - private final View mMenuFrameView; - private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>(); private final View mPipFrameView; + private final View mMenuFrameView; private final View mPipView; - private final TextView mEduTextView; - private final View mEduTextContainerView; + + private final View mPipBackground; + private final View mDimLayer; + + private final TvPipMenuEduTextDrawer mEduTextDrawer; + private final int mPipMenuOuterSpace; private final int mPipMenuBorderWidth; - private final int mEduTextFadeExitAnimationDurationMs; - private final int mEduTextSlideExitAnimationDurationMs; - private int mEduTextHeight; + + private final int mPipMenuFadeAnimationDuration; + private final int mResizeAnimationDuration; private final ImageView mArrowUp; private final ImageView mArrowRight; private final ImageView mArrowDown; private final ImageView mArrowLeft; - - private final ScrollView mScrollView; - private final HorizontalScrollView mHorizontalScrollView; - private View mFocusedButton; + private final TvWindowMenuActionButton mA11yDoneButton; private Rect mCurrentPipBounds; private boolean mMoveMenuIsVisible; private boolean mButtonMenuIsVisible; - - private final TvPipMenuActionButton mExpandButton; - private final TvPipMenuActionButton mCloseButton; - private boolean mSwitchingOrientation; - private final int mPipMenuFadeAnimationDuration; - private final int mResizeAnimationDuration; - - public TvPipMenuView(@NonNull Context context) { - this(context, null); - } - - public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + private final AccessibilityManager mA11yManager; + private final Handler mMainHandler; + public TvPipMenuView(@NonNull Context context, @NonNull Handler mainHandler, + @NonNull Listener listener, TvPipActionsProvider tvPipActionsProvider) { + super(context, null, 0, 0); inflate(context, R.layout.tv_pip_menu, this); - mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons); - mActionButtonsContainer.findViewById(R.id.tv_pip_menu_fullscreen_button) - .setOnClickListener(this); + mMainHandler = mainHandler; + mListener = listener; + mA11yManager = context.getSystemService(AccessibilityManager.class); - mCloseButton = mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button); - mCloseButton.setOnClickListener(this); - mCloseButton.setIsCustomCloseAction(true); + mActionButtonsRecyclerView = findViewById(R.id.tv_pip_menu_action_buttons); + mButtonLayoutManager = new LinearLayoutManager(mContext); + mActionButtonsRecyclerView.setLayoutManager(mButtonLayoutManager); + mActionButtonsRecyclerView.setPreserveFocusAfterLayout(true); - mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button) - .setOnClickListener(this); - mExpandButton = findViewById(R.id.tv_pip_menu_expand_button); - mExpandButton.setOnClickListener(this); + mTvPipActionsProvider = tvPipActionsProvider; + mRecyclerViewAdapter = new RecyclerViewAdapter(tvPipActionsProvider.getActionsList()); + mActionButtonsRecyclerView.setAdapter(mRecyclerViewAdapter); - mScrollView = findViewById(R.id.tv_pip_menu_scroll); - mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll); + tvPipActionsProvider.addListener(this); mMenuFrameView = findViewById(R.id.tv_pip_menu_frame); mPipFrameView = findViewById(R.id.tv_pip_border); mPipView = findViewById(R.id.tv_pip); + mPipBackground = findViewById(R.id.tv_pip_menu_background); + mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer); + mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up); mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right); mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down); mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left); - - mEduTextView = findViewById(R.id.tv_pip_menu_edu_text); - mEduTextContainerView = findViewById(R.id.tv_pip_menu_edu_text_container); + mA11yDoneButton = findViewById(R.id.tv_pip_menu_done_button); mResizeAnimationDuration = context.getResources().getInteger( R.integer.config_pipResizeAnimationDuration); mPipMenuFadeAnimationDuration = context.getResources() - .getInteger(R.integer.pip_menu_fade_animation_duration); + .getInteger(R.integer.tv_window_menu_fade_animation_duration); mPipMenuOuterSpace = context.getResources() .getDimensionPixelSize(R.dimen.pip_menu_outer_space); mPipMenuBorderWidth = context.getResources() .getDimensionPixelSize(R.dimen.pip_menu_border_width); - mEduTextHeight = context.getResources() - .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height); - mEduTextFadeExitAnimationDurationMs = context.getResources() - .getInteger(R.integer.pip_edu_text_view_exit_animation_duration_ms); - mEduTextSlideExitAnimationDurationMs = context.getResources() - .getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms); - - initEduText(); - } - void initEduText() { - final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text); - final SpannableString spannableString = new SpannableString(eduText); - Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst() - .ifPresent(annotation -> { - final Drawable icon = - getResources().getDrawable(R.drawable.home_icon, mContext.getTheme()); - if (icon != null) { - icon.mutate(); - icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); - spannableString.setSpan(new CenteredImageSpan(icon), - eduText.getSpanStart(annotation), - eduText.getSpanEnd(annotation), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - }); - - mEduTextView.setText(spannableString); - } - - void setEduTextActive(boolean active) { - mEduTextView.setSelected(active); + mEduTextDrawer = new TvPipMenuEduTextDrawer(mContext, mainHandler, mListener); + ((FrameLayout) findViewById(R.id.tv_pip_menu_edu_text_drawer_placeholder)) + .addView(mEduTextDrawer); } - void hideEduText() { - final ValueAnimator heightAnimation = ValueAnimator.ofInt(mEduTextHeight, 0); - heightAnimation.setDuration(mEduTextSlideExitAnimationDurationMs); - heightAnimation.setInterpolator(TvPipInterpolators.BROWSE); - heightAnimation.addUpdateListener(animator -> { - mEduTextHeight = (int) animator.getAnimatedValue(); - }); - mEduTextView.animate() - .alpha(0f) - .setInterpolator(TvPipInterpolators.EXIT) - .setDuration(mEduTextFadeExitAnimationDurationMs) - .withEndAction(() -> { - mEduTextContainerView.setVisibility(GONE); - }).start(); - heightAnimation.start(); - } + void onPipTransitionToTargetBoundsStarted(Rect targetBounds) { + if (targetBounds == null) { + return; + } - void onPipTransitionStarted(Rect finishBounds) { // Fade out content by fading in view on top. - if (mCurrentPipBounds != null && finishBounds != null) { + if (mCurrentPipBounds != null) { boolean ratioChanged = PipUtils.aspectRatioChanged( mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(), - finishBounds.width() / (float) finishBounds.height()); + targetBounds.width() / (float) targetBounds.height()); if (ratioChanged) { - mPipView.animate() + mPipBackground.animate() .alpha(1f) .setInterpolator(TvPipInterpolators.EXIT) .setDuration(mResizeAnimationDuration / 2) @@ -232,52 +162,54 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { } // Update buttons. - final boolean vertical = finishBounds.height() > finishBounds.width(); + final boolean vertical = targetBounds.height() > targetBounds.width(); final boolean orientationChanged = - vertical != (mActionButtonsContainer.getOrientation() == LinearLayout.VERTICAL); + vertical != (mButtonLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL); ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onPipTransitionStarted(), orientation changed %b", TAG, orientationChanged); + "%s: onPipTransitionToTargetBoundsStarted(), orientation changed %b", + TAG, orientationChanged); if (!orientationChanged) { return; } if (mButtonMenuIsVisible) { mSwitchingOrientation = true; - mActionButtonsContainer.animate() + mActionButtonsRecyclerView.animate() .alpha(0) .setInterpolator(TvPipInterpolators.EXIT) .setDuration(mResizeAnimationDuration / 2) .withEndAction(() -> { - changeButtonScrollOrientation(finishBounds); - updateButtonGravity(finishBounds); + mButtonLayoutManager.setOrientation(vertical + ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL); // Only make buttons visible again in onPipTransitionFinished to keep in // sync with PiP content alpha animation. }); } else { - changeButtonScrollOrientation(finishBounds); - updateButtonGravity(finishBounds); + mButtonLayoutManager.setOrientation(vertical + ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL); } } - void onPipTransitionFinished() { + void onPipTransitionFinished(boolean enterTransition) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onPipTransitionFinished()", TAG); - // Fade in content by fading out view on top. - mPipView.animate() + // Fade in content by fading out view on top (faded out at every aspect ratio change). + mPipBackground.animate() .alpha(0f) .setDuration(mResizeAnimationDuration / 2) .setInterpolator(TvPipInterpolators.ENTER) .start(); - // Update buttons. + if (enterTransition) { + mEduTextDrawer.init(); + } + if (mSwitchingOrientation) { - mActionButtonsContainer.animate() + mActionButtonsRecyclerView.animate() .alpha(1) .setInterpolator(TvPipInterpolators.ENTER) .setDuration(mResizeAnimationDuration / 2); - } else { - refocusPreviousButton(); } mSwitchingOrientation = false; } @@ -290,111 +222,13 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { "%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(), updatedBounds.height()); mCurrentPipBounds = updatedBounds; - if (!mSwitchingOrientation) { - updateButtonGravity(mCurrentPipBounds); - } - updatePipFrameBounds(); } - private void changeButtonScrollOrientation(Rect bounds) { - final boolean vertical = bounds.height() > bounds.width(); - - final ViewGroup oldScrollView = vertical ? mHorizontalScrollView : mScrollView; - final ViewGroup newScrollView = vertical ? mScrollView : mHorizontalScrollView; - - if (oldScrollView.getChildCount() == 1) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: orientation changed", TAG); - oldScrollView.removeView(mActionButtonsContainer); - oldScrollView.setVisibility(GONE); - mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL - : LinearLayout.HORIZONTAL); - newScrollView.addView(mActionButtonsContainer); - newScrollView.setVisibility(VISIBLE); - if (mFocusedButton != null) { - mFocusedButton.requestFocus(); - } - } - } - - /** - * Change button gravity based on new dimensions - */ - private void updateButtonGravity(Rect bounds) { - final boolean vertical = bounds.height() > bounds.width(); - // Use Math.max since the possible orientation change might not have been applied yet. - final int buttonsSize = Math.max(mActionButtonsContainer.getHeight(), - mActionButtonsContainer.getWidth()); - - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: buttons container width: %s, height: %s", TAG, - mActionButtonsContainer.getWidth(), mActionButtonsContainer.getHeight()); - - final boolean buttonsFit = - vertical ? buttonsSize < bounds.height() - : buttonsSize < bounds.width(); - final int buttonGravity = buttonsFit ? Gravity.CENTER - : (vertical ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL); - - final LayoutParams params = (LayoutParams) mActionButtonsContainer.getLayoutParams(); - params.gravity = buttonGravity; - mActionButtonsContainer.setLayoutParams(params); - - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: vertical: %b, buttonsFit: %b, gravity: %s", TAG, vertical, buttonsFit, - Gravity.toString(buttonGravity)); - } - - private void refocusPreviousButton() { - if (mMoveMenuIsVisible || mCurrentPipBounds == null || mFocusedButton == null) { - return; - } - final boolean vertical = mCurrentPipBounds.height() > mCurrentPipBounds.width(); - - if (!mFocusedButton.hasFocus()) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: request focus from: %s", TAG, mFocusedButton); - mFocusedButton.requestFocus(); - } else { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: already focused: %s", TAG, mFocusedButton); - } - - // Do we need to scroll? - final Rect buttonBounds = new Rect(); - final Rect scrollBounds = new Rect(); - if (vertical) { - mScrollView.getDrawingRect(scrollBounds); - } else { - mHorizontalScrollView.getDrawingRect(scrollBounds); - } - mFocusedButton.getHitRect(buttonBounds); - - if (scrollBounds.contains(buttonBounds)) { - // Button is already completely visible, don't scroll - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: not scrolling", TAG); - return; - } - - // Scrolling so the button is visible to the user. - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: scrolling to focused button", TAG); - - if (vertical) { - mScrollView.smoothScrollTo((int) mFocusedButton.getX(), - (int) mFocusedButton.getY()); - } else { - mHorizontalScrollView.smoothScrollTo((int) mFocusedButton.getX(), - (int) mFocusedButton.getY()); - } - } - Rect getPipMenuContainerBounds(Rect pipBounds) { final Rect menuUiBounds = new Rect(pipBounds); menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace); - menuUiBounds.bottom += mEduTextHeight; + menuUiBounds.bottom += mEduTextDrawer.getHeight(); return menuUiBounds; } @@ -420,78 +254,78 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { mPipView.setLayoutParams(pipViewParams); } - - } - - void setListener(@Nullable Listener listener) { - mListener = listener; - } - - void setExpandedModeEnabled(boolean enabled) { - mExpandButton.setVisibility(enabled ? VISIBLE : GONE); - } - - void setIsExpanded(boolean expanded) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: setIsExpanded, expanded: %b", TAG, expanded); + // Keep focused button within the visible area while the PiP is changing size. Otherwise, + // the button would lose focus which would cause a need for scrolling and re-focusing after + // the animation finishes, which does not look good. + View focusedChild = mActionButtonsRecyclerView.getFocusedChild(); + if (focusedChild != null) { + mActionButtonsRecyclerView.scrollToPosition( + mActionButtonsRecyclerView.getChildLayoutPosition(focusedChild)); } - mExpandButton.setImageResource( - expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand); - mExpandButton.setTextAndDescription( - expanded ? R.string.pip_collapse : R.string.pip_expand); } /** * @param gravity for the arrow hints */ void showMoveMenu(int gravity) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG); - } - mButtonMenuIsVisible = false; - mMoveMenuIsVisible = true; - showButtonsMenu(false); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG); showMovementHints(gravity); + setMenuButtonsVisible(false); setFrameHighlighted(true); + + animateAlphaTo(mA11yManager.isEnabled() ? 1f : 0f, mDimLayer); + + mEduTextDrawer.closeIfNeeded(); } - void showButtonsMenu() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: showButtonsMenu()", TAG); - } - mButtonMenuIsVisible = true; - mMoveMenuIsVisible = false; - showButtonsMenu(true); + void showButtonsMenu(boolean exitingMoveMode) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode); + setMenuButtonsVisible(true); hideMovementHints(); setFrameHighlighted(true); + animateAlphaTo(1f, mDimLayer); + mEduTextDrawer.closeIfNeeded(); - // Always focus on the first button when opening the menu, except directly after moving. - if (mFocusedButton == null) { - // Focus on first button (there is a Space at position 0) - mFocusedButton = mActionButtonsContainer.getChildAt(1); - // Reset scroll position. - mScrollView.scrollTo(0, 0); - mHorizontalScrollView.scrollTo( - isLayoutRtl() ? mActionButtonsContainer.getWidth() : 0, 0); + if (exitingMoveMode) { + scrollAndRefocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE), + /* alwaysScroll= */ false); + } else { + scrollAndRefocusButton(0, /* alwaysScroll= */ true); + } + } + + private void scrollAndRefocusButton(int position, boolean alwaysScroll) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: scrollAndRefocusButton, target: %d", TAG, position); + + if (alwaysScroll || !refocusButton(position)) { + mButtonLayoutManager.scrollToPositionWithOffset(position, 0); + mActionButtonsRecyclerView.post(() -> refocusButton(position)); } - refocusPreviousButton(); } /** - * Hides all menu views, including the menu frame. + * @return true if focus was requested, false if focus request could not be carried out due to + * the view for the position not being available (scrolling beforehand will be necessary). */ + private boolean refocusButton(int position) { + View itemToFocus = mButtonLayoutManager.findViewByPosition(position); + if (itemToFocus != null) { + itemToFocus.requestFocus(); + itemToFocus.requestAccessibilityFocus(); + } + return itemToFocus != null; + } + void hideAllUserControls() { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hideAllUserControls()", TAG); - mFocusedButton = null; - mButtonMenuIsVisible = false; - mMoveMenuIsVisible = false; - showButtonsMenu(false); + setMenuButtonsVisible(false); hideMovementHints(); setFrameHighlighted(false); + animateAlphaTo(0f, mDimLayer); } @Override @@ -522,143 +356,30 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { }); } - /** - * Button order: - * - Fullscreen - * - Close - * - Custom actions (app or media actions) - * - System actions - */ - void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction, - Handler mainHandler) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: setAdditionalActions()", TAG); - } - - // Replace system close action with custom close action if available - if (closeAction != null) { - setActionForButton(closeAction, mCloseButton, mainHandler); - } else { - mCloseButton.setTextAndDescription(R.string.pip_close); - mCloseButton.setImageResource(R.drawable.pip_ic_close_white); - } - mCloseButton.setIsCustomCloseAction(closeAction != null); - // Make sure the close action is always enabled - mCloseButton.setEnabled(true); - - // Make sure we exactly as many additional buttons as we have actions to display. - final int actionsNumber = actions.size(); - int buttonsNumber = mAdditionalButtons.size(); - if (actionsNumber > buttonsNumber) { - // Add buttons until we have enough to display all the actions. - while (actionsNumber > buttonsNumber) { - TvPipMenuActionButton button = new TvPipMenuActionButton(mContext); - button.setOnClickListener(this); - - mActionButtonsContainer.addView(button, - FIRST_CUSTOM_ACTION_POSITION + buttonsNumber); - mAdditionalButtons.add(button); - - buttonsNumber++; - } - } else if (actionsNumber < buttonsNumber) { - // Hide buttons until we as many as the actions. - while (actionsNumber < buttonsNumber) { - final View button = mAdditionalButtons.get(buttonsNumber - 1); - button.setVisibility(View.GONE); - button.setTag(null); - - buttonsNumber--; - } - } - - // "Assign" actions to the buttons. - for (int index = 0; index < actionsNumber; index++) { - final RemoteAction action = actions.get(index); - final TvPipMenuActionButton button = mAdditionalButtons.get(index); - - // Remove action if it matches the custom close action. - if (PipUtils.remoteActionsMatch(action, closeAction)) { - button.setVisibility(GONE); - continue; - } - setActionForButton(action, button, mainHandler); - } - - if (mCurrentPipBounds != null) { - updateButtonGravity(mCurrentPipBounds); - refocusPreviousButton(); - } - } - - private void setActionForButton(RemoteAction action, TvPipMenuActionButton button, - Handler mainHandler) { - button.setVisibility(View.VISIBLE); // Ensure the button is visible. - if (action.getContentDescription().length() > 0) { - button.setTextAndDescription(action.getContentDescription()); - } else { - button.setTextAndDescription(action.getTitle()); - } - button.setEnabled(action.isEnabled()); - button.setTag(action); - action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler); - } - - @Nullable - SurfaceControl getWindowSurfaceControl() { - final ViewRootImpl root = getViewRootImpl(); - if (root == null) { - return null; - } - final SurfaceControl out = root.getSurfaceControl(); - if (out != null && out.isValid()) { - return out; - } - return null; - } - @Override - public void onClick(View v) { - if (mListener == null) return; - - final int id = v.getId(); - if (id == R.id.tv_pip_menu_fullscreen_button) { - mListener.onFullscreenButtonClick(); - } else if (id == R.id.tv_pip_menu_move_button) { - mListener.onEnterMoveMode(); - } else if (id == R.id.tv_pip_menu_close_button) { - mListener.onCloseButtonClick(); - } else if (id == R.id.tv_pip_menu_expand_button) { - mListener.onToggleExpandedMode(); - } else { - // This should be an "additional action" - final RemoteAction action = (RemoteAction) v.getTag(); - if (action != null) { - try { - action.getActionIntent().send(); - } catch (PendingIntent.CanceledException e) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Failed to send action, %s", TAG, e); - } - } else { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: RemoteAction is null", TAG); - } + public void onActionsChanged(int added, int updated, int startIndex) { + mRecyclerViewAdapter.notifyItemRangeChanged(startIndex, updated); + if (added > 0) { + mRecyclerViewAdapter.notifyItemRangeInserted(startIndex + updated, added); + } else if (added < 0) { + mRecyclerViewAdapter.notifyItemRangeRemoved(startIndex + updated, -added); } } @Override public boolean dispatchKeyEvent(KeyEvent event) { - if (mListener != null && event.getAction() == ACTION_UP) { - if (!mMoveMenuIsVisible) { - mFocusedButton = mActionButtonsContainer.getFocusedChild(); + if (event.getAction() == ACTION_UP) { + + if (event.getKeyCode() == KEYCODE_BACK) { + mListener.onBackPress(); + return true; + } + + if (mA11yManager.isEnabled()) { + return super.dispatchKeyEvent(event); } switch (event.getKeyCode()) { - case KEYCODE_BACK: - mListener.onBackPress(); - return true; case KEYCODE_DPAD_UP: case KEYCODE_DPAD_DOWN: case KEYCODE_DPAD_LEFT: @@ -679,15 +400,39 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { * Shows user hints for moving the PiP, e.g. arrows. */ public void showMovementHints(int gravity) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity)); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity)); + mMoveMenuIsVisible = true; animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp); animateAlphaTo(checkGravity(gravity, Gravity.TOP) ? 1f : 0f, mArrowDown); animateAlphaTo(checkGravity(gravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft); animateAlphaTo(checkGravity(gravity, Gravity.LEFT) ? 1f : 0f, mArrowRight); + + boolean a11yEnabled = mA11yManager.isEnabled(); + setArrowA11yEnabled(mArrowUp, a11yEnabled, KEYCODE_DPAD_UP); + setArrowA11yEnabled(mArrowDown, a11yEnabled, KEYCODE_DPAD_DOWN); + setArrowA11yEnabled(mArrowLeft, a11yEnabled, KEYCODE_DPAD_LEFT); + setArrowA11yEnabled(mArrowRight, a11yEnabled, KEYCODE_DPAD_RIGHT); + + animateAlphaTo(a11yEnabled ? 1f : 0f, mA11yDoneButton); + if (a11yEnabled) { + mA11yDoneButton.setVisibility(VISIBLE); + mA11yDoneButton.setOnClickListener(v -> { + mListener.onExitMoveMode(); + }); + mA11yDoneButton.requestFocus(); + mA11yDoneButton.requestAccessibilityFocus(); + } + } + + private void setArrowA11yEnabled(View arrowView, boolean enabled, int keycode) { + arrowView.setClickable(enabled); + if (enabled) { + arrowView.setOnClickListener(v -> { + mListener.onPipMovement(keycode); + }); + } } private boolean checkGravity(int gravity, int feature) { @@ -698,40 +443,84 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { * Hides user hints for moving the PiP, e.g. arrows. */ public void hideMovementHints() { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: hideMovementHints()", TAG); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: hideMovementHints()", TAG); + + if (!mMoveMenuIsVisible) { + return; } + mMoveMenuIsVisible = false; + animateAlphaTo(0, mArrowUp); animateAlphaTo(0, mArrowRight); animateAlphaTo(0, mArrowDown); animateAlphaTo(0, mArrowLeft); + animateAlphaTo(0, mA11yDoneButton); } /** * Show or hide the pip buttons menu. */ - public void showButtonsMenu(boolean show) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: showUserActions: %b", TAG, show); - } - if (show) { - mActionButtonsContainer.setVisibility(VISIBLE); - refocusPreviousButton(); - } - animateAlphaTo(show ? 1 : 0, mActionButtonsContainer); + private void setMenuButtonsVisible(boolean visible) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: showUserActions: %b", TAG, visible); + mButtonMenuIsVisible = visible; + animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView); } private void setFrameHighlighted(boolean highlighted) { mMenuFrameView.setActivated(highlighted); } - interface Listener { + private class RecyclerViewAdapter extends + RecyclerView.Adapter<RecyclerViewAdapter.ButtonViewHolder> { - void onBackPress(); + private final List<TvPipAction> mActionList; - void onEnterMoveMode(); + RecyclerViewAdapter(List<TvPipAction> actionList) { + this.mActionList = actionList; + } + + @NonNull + @Override + public ButtonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ButtonViewHolder(new TvWindowMenuActionButton(mContext)); + } + + @Override + public void onBindViewHolder(@NonNull ButtonViewHolder holder, int position) { + TvPipAction action = mActionList.get(position); + action.populateButton(holder.mButton, mMainHandler); + } + + @Override + public int getItemCount() { + return mActionList.size(); + } + + private class ButtonViewHolder extends RecyclerView.ViewHolder implements OnClickListener { + TvWindowMenuActionButton mButton; + + ButtonViewHolder(@NonNull View itemView) { + super(itemView); + mButton = (TvWindowMenuActionButton) itemView; + mButton.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + TvPipAction action = mActionList.get( + mActionButtonsRecyclerView.getChildLayoutPosition(v)); + if (action != null) { + action.executeAction(); + } + } + } + } + + interface Listener extends TvPipMenuEduTextDrawer.Listener { + + void onBackPress(); /** * Called when a button for exiting move mode was pressed. @@ -745,11 +534,5 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { * @return whether pip movement was handled. */ boolean onPipMovement(int keycode); - - void onCloseButtonClick(); - - void onFullscreenButtonClick(); - - void onToggleExpandedMode(); } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java index 61a609d9755e..f22ee595e6c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java @@ -16,18 +16,13 @@ package com.android.wm.shell.pip.tv; -import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE; -import static android.app.Notification.Action.SEMANTIC_ACTION_NONE; - +import android.annotation.NonNull; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.RemoteAction; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; @@ -35,7 +30,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.session.MediaSession; import android.os.Bundle; -import android.os.Handler; import android.text.TextUtils; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; @@ -47,7 +41,6 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import java.util.ArrayList; import java.util.List; /** @@ -55,39 +48,18 @@ import java.util.List; * <p>Once it's created, it will manage the PiP notification UI by itself except for handling * configuration changes and user initiated expanded PiP toggling. */ -public class TvPipNotificationController { - private static final String TAG = "TvPipNotification"; +public class TvPipNotificationController implements TvPipActionsProvider.Listener { + private static final String TAG = TvPipNotificationController.class.getSimpleName(); // Referenced in com.android.systemui.util.NotificationChannels. public static final String NOTIFICATION_CHANNEL = "TVPIP"; private static final String NOTIFICATION_TAG = "TvPip"; - private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"; - - private static final String ACTION_SHOW_PIP_MENU = - "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU"; - private static final String ACTION_CLOSE_PIP = - "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP"; - private static final String ACTION_MOVE_PIP = - "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP"; - private static final String ACTION_TOGGLE_EXPANDED_PIP = - "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP"; - private static final String ACTION_FULLSCREEN = - "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN"; private final Context mContext; private final PackageManager mPackageManager; private final NotificationManager mNotificationManager; private final Notification.Builder mNotificationBuilder; - private final ActionBroadcastReceiver mActionBroadcastReceiver; - private final Handler mMainHandler; - private Delegate mDelegate; - private final TvPipBoundsState mTvPipBoundsState; - - private String mDefaultTitle; - - private final List<RemoteAction> mCustomActions = new ArrayList<>(); - private final List<RemoteAction> mMediaActions = new ArrayList<>(); - private RemoteAction mCustomCloseAction; + private TvPipActionsProvider mTvPipActionsProvider; private MediaSession.Token mMediaSessionToken; @@ -95,55 +67,41 @@ public class TvPipNotificationController { private String mPackageName; private boolean mIsNotificationShown; + private String mDefaultTitle; private String mPipTitle; private String mPipSubtitle; + // Saving the actions, so they don't have to be regenerated when e.g. the PiP title changes. + @NonNull + private Notification.Action[] mPipActions; + private Bitmap mActivityIcon; public TvPipNotificationController(Context context, PipMediaController pipMediaController, - PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState, - Handler mainHandler) { + PipParamsChangedForwarder pipParamsChangedForwarder) { mContext = context; mPackageManager = context.getPackageManager(); mNotificationManager = context.getSystemService(NotificationManager.class); - mMainHandler = mainHandler; - mTvPipBoundsState = tvPipBoundsState; + + mPipActions = new Notification.Action[0]; mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL) .setLocalOnly(true) .setOngoing(true) .setCategory(Notification.CATEGORY_SYSTEM) .setShowWhen(true) + .setOnlyAlertOnce(true) .setSmallIcon(R.drawable.pip_icon) .setAllowSystemGeneratedContextualActions(false) - .setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN)) - .setDeleteIntent(getCloseAction().actionIntent) - .extend(new Notification.TvExtender() - .setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU)) - .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP))); - - mActionBroadcastReceiver = new ActionBroadcastReceiver(); + .setContentIntent( + createPendingIntent(context, TvPipController.ACTION_TO_FULLSCREEN)); + // TvExtender and DeleteIntent set later since they might change. - pipMediaController.addActionListener(this::onMediaActionsChanged); pipMediaController.addTokenListener(this::onMediaSessionTokenChanged); pipParamsChangedForwarder.addListener( new PipParamsChangedForwarder.PipParamsChangedCallback() { @Override - public void onExpandedAspectRatioChanged(float ratio) { - updateExpansionState(); - } - - @Override - public void onActionsChanged(List<RemoteAction> actions, - RemoteAction closeAction) { - mCustomActions.clear(); - mCustomActions.addAll(actions); - mCustomCloseAction = closeAction; - updateNotificationContent(); - } - - @Override public void onTitleChanged(String title) { mPipTitle = title; updateNotificationContent(); @@ -156,34 +114,33 @@ public class TvPipNotificationController { } }); - onConfigurationChanged(context); + onConfigurationChanged(); } - void setDelegate(Delegate delegate) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: setDelegate(), delegate=%s", - TAG, delegate); - - if (mDelegate != null) { - throw new IllegalStateException( - "The delegate has already been set and should not change."); - } - if (delegate == null) { - throw new IllegalArgumentException("The delegate must not be null."); - } + /** + * Call before showing any notification. + */ + void setTvPipActionsProvider(@NonNull TvPipActionsProvider tvPipActionsProvider) { + mTvPipActionsProvider = tvPipActionsProvider; + mTvPipActionsProvider.addListener(this); + } - mDelegate = delegate; + void onConfigurationChanged() { + mDefaultTitle = mContext.getResources().getString(R.string.pip_notification_unknown_title); + updateNotificationContent(); } void show(String packageName) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName); - if (mDelegate == null) { - throw new IllegalStateException("Delegate is not set."); + if (mTvPipActionsProvider == null) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Missing TvPipActionsProvider", TAG); + return; } mIsNotificationShown = true; mPackageName = packageName; mActivityIcon = getActivityIcon(); - mActionBroadcastReceiver.register(); updateNotificationContent(); } @@ -193,151 +150,42 @@ public class TvPipNotificationController { mIsNotificationShown = false; mPackageName = null; - mActionBroadcastReceiver.unregister(); - mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP); } - private Notification.Action getToggleAction(boolean expanded) { - if (expanded) { - return createSystemAction(R.drawable.pip_ic_collapse, - R.string.pip_collapse, ACTION_TOGGLE_EXPANDED_PIP); - } else { - return createSystemAction(R.drawable.pip_ic_expand, R.string.pip_expand, - ACTION_TOGGLE_EXPANDED_PIP); - } - } - - private Notification.Action createSystemAction(int iconRes, int titleRes, String action) { - Notification.Action.Builder builder = new Notification.Action.Builder( - Icon.createWithResource(mContext, iconRes), - mContext.getString(titleRes), - createPendingIntent(mContext, action)); - builder.setContextual(true); - return builder.build(); - } - - private void onMediaActionsChanged(List<RemoteAction> actions) { - mMediaActions.clear(); - mMediaActions.addAll(actions); - if (mCustomActions.isEmpty()) { - updateNotificationContent(); - } - } - private void onMediaSessionTokenChanged(MediaSession.Token token) { mMediaSessionToken = token; updateNotificationContent(); } - private Notification.Action remoteToNotificationAction(RemoteAction action) { - return remoteToNotificationAction(action, SEMANTIC_ACTION_NONE); - } - - private Notification.Action remoteToNotificationAction(RemoteAction action, - int semanticAction) { - Notification.Action.Builder builder = new Notification.Action.Builder(action.getIcon(), - action.getTitle(), - action.getActionIntent()); - if (action.getContentDescription() != null) { - Bundle extras = new Bundle(); - extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION, - action.getContentDescription()); - builder.addExtras(extras); - } - builder.setSemanticAction(semanticAction); - builder.setContextual(true); - return builder.build(); - } - - private Notification.Action[] getNotificationActions() { - final List<Notification.Action> actions = new ArrayList<>(); - - // 1. Fullscreen - actions.add(getFullscreenAction()); - // 2. Close - actions.add(getCloseAction()); - // 3. App actions - final List<RemoteAction> appActions = - mCustomActions.isEmpty() ? mMediaActions : mCustomActions; - for (RemoteAction appAction : appActions) { - if (PipUtils.remoteActionsMatch(mCustomCloseAction, appAction) - || !appAction.isEnabled()) { - continue; - } - actions.add(remoteToNotificationAction(appAction)); - } - // 4. Move - actions.add(getMoveAction()); - // 5. Toggle expansion (if expanded PiP enabled) - if (mTvPipBoundsState.getDesiredTvExpandedAspectRatio() > 0 - && mTvPipBoundsState.isTvExpandedPipSupported()) { - actions.add(getToggleAction(mTvPipBoundsState.isTvPipExpanded())); - } - return actions.toArray(new Notification.Action[0]); - } - - private Notification.Action getCloseAction() { - if (mCustomCloseAction == null) { - return createSystemAction(R.drawable.pip_ic_close_white, R.string.pip_close, - ACTION_CLOSE_PIP); - } else { - return remoteToNotificationAction(mCustomCloseAction, SEMANTIC_ACTION_DELETE); - } - } - - private Notification.Action getFullscreenAction() { - return createSystemAction(R.drawable.pip_ic_fullscreen_white, - R.string.pip_fullscreen, ACTION_FULLSCREEN); - } - - private Notification.Action getMoveAction() { - return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move, - ACTION_MOVE_PIP); - } - - /** - * Called by {@link TvPipController} when the configuration is changed. - */ - void onConfigurationChanged(Context context) { - mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title); - updateNotificationContent(); - } - - void updateExpansionState() { - updateNotificationContent(); - } - private void updateNotificationContent() { if (mPackageManager == null || !mIsNotificationShown) { return; } - Notification.Action[] actions = getNotificationActions(); ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: update(), title: %s, subtitle: %s, mediaSessionToken: %s, #actions: %s", TAG, - getNotificationTitle(), mPipSubtitle, mMediaSessionToken, actions.length); - for (Notification.Action action : actions) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: action: %s", TAG, - action.toString()); - } - + getNotificationTitle(), mPipSubtitle, mMediaSessionToken, mPipActions.length); mNotificationBuilder .setWhen(System.currentTimeMillis()) .setContentTitle(getNotificationTitle()) .setContentText(mPipSubtitle) .setSubText(getApplicationLabel(mPackageName)) - .setActions(actions); + .setActions(mPipActions); setPipIcon(); Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken); mNotificationBuilder.setExtras(extras); + PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent(); + mNotificationBuilder.setDeleteIntent(closeIntent); // TvExtender not recognized if not set last. mNotificationBuilder.extend(new Notification.TvExtender() - .setContentIntent(createPendingIntent(mContext, ACTION_SHOW_PIP_MENU)) - .setDeleteIntent(createPendingIntent(mContext, ACTION_CLOSE_PIP))); + .setContentIntent( + createPendingIntent(mContext, TvPipController.ACTION_SHOW_PIP_MENU)) + .setDeleteIntent(closeIntent)); + mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP, mNotificationBuilder.build()); } @@ -389,68 +237,20 @@ public class TvPipNotificationController { return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true); } - private static PendingIntent createPendingIntent(Context context, String action) { + static PendingIntent createPendingIntent(Context context, String action) { return PendingIntent.getBroadcast(context, 0, new Intent(action).setPackage(context.getPackageName()), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } - private class ActionBroadcastReceiver extends BroadcastReceiver { - final IntentFilter mIntentFilter; - { - mIntentFilter = new IntentFilter(); - mIntentFilter.addAction(ACTION_CLOSE_PIP); - mIntentFilter.addAction(ACTION_SHOW_PIP_MENU); - mIntentFilter.addAction(ACTION_MOVE_PIP); - mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP); - mIntentFilter.addAction(ACTION_FULLSCREEN); - } - boolean mRegistered = false; - - void register() { - if (mRegistered) return; - - mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION, - mMainHandler); - mRegistered = true; - } - - void unregister() { - if (!mRegistered) return; - - mContext.unregisterReceiver(this); - mRegistered = false; - } - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: on(Broadcast)Receive(), action=%s", TAG, action); - - if (ACTION_SHOW_PIP_MENU.equals(action)) { - mDelegate.showPictureInPictureMenu(); - } else if (ACTION_CLOSE_PIP.equals(action)) { - mDelegate.closePip(); - } else if (ACTION_MOVE_PIP.equals(action)) { - mDelegate.enterPipMovementMenu(); - } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) { - mDelegate.togglePipExpansion(); - } else if (ACTION_FULLSCREEN.equals(action)) { - mDelegate.movePipToFullscreen(); - } + @Override + public void onActionsChanged(int added, int updated, int startIndex) { + List<TvPipAction> actions = mTvPipActionsProvider.getActionsList(); + mPipActions = new Notification.Action[actions.size()]; + for (int i = 0; i < mPipActions.length; i++) { + mPipActions[i] = actions.get(i).toNotificationAction(mContext); } + updateNotificationContent(); } - interface Delegate { - void showPictureInPictureMenu(); - - void closePip(); - - void enterPipMovementMenu(); - - void togglePipExpansion(); - - void movePipToFullscreen(); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java new file mode 100644 index 000000000000..93b6a908e3f4 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 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.pip.tv; + +import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE; +import static android.app.Notification.Action.SEMANTIC_ACTION_NONE; + +import android.annotation.DrawableRes; +import android.annotation.NonNull; +import android.annotation.StringRes; +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.graphics.drawable.Icon; +import android.os.Handler; + +import com.android.wm.shell.common.TvWindowMenuActionButton; + +/** + * A TvPipAction for actions that the system provides, i.e. fullscreen, default close, move, + * expand/collapse. + */ +public class TvPipSystemAction extends TvPipAction { + + @StringRes + private int mTitleResource; + @DrawableRes + private int mIconResource; + + private final PendingIntent mBroadcastIntent; + + TvPipSystemAction(@ActionType int actionType, @StringRes int title, @DrawableRes int icon, + String broadcastAction, @NonNull Context context, + SystemActionsHandler systemActionsHandler) { + super(actionType, systemActionsHandler); + update(title, icon); + mBroadcastIntent = TvPipNotificationController.createPendingIntent(context, + broadcastAction); + } + + void update(@StringRes int title, @DrawableRes int icon) { + mTitleResource = title; + mIconResource = icon; + } + + void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) { + button.setTextAndDescription(mTitleResource); + button.setImageResource(mIconResource); + button.setEnabled(true); + } + + PendingIntent getPendingIntent() { + return mBroadcastIntent; + } + + @Override + Notification.Action toNotificationAction(Context context) { + Notification.Action.Builder builder = new Notification.Action.Builder( + Icon.createWithResource(context, mIconResource), + context.getString(mTitleResource), + mBroadcastIntent); + + builder.setSemanticAction(isCloseAction() + ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE); + builder.setContextual(true); + return builder.build(); + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS new file mode 100644 index 000000000000..28be0efc38f6 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS @@ -0,0 +1,3 @@ +# WM shell sub-module TV splitscreen owner +galinap@google.com +bronger@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java new file mode 100644 index 000000000000..1d8a8d506c5c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2022 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.splitscreen.tv; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; +import static android.view.WindowManager.SHELL_ROOT_LAYER_DIVIDER; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.PixelFormat; +import android.os.Handler; +import android.os.RemoteException; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; +import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.split.SplitScreenConstants; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +/** + * Handles the interaction logic with the {@link TvSplitMenuView}. + * A bridge between {@link TvStageCoordinator} and {@link TvSplitMenuView}. + */ +public class TvSplitMenuController implements TvSplitMenuView.Listener { + + private static final String TAG = TvSplitMenuController.class.getSimpleName(); + private static final String ACTION_SHOW_MENU = "com.android.wm.shell.splitscreen.SHOW_MENU"; + private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"; + + private final Context mContext; + private final StageController mStageController; + private final SystemWindows mSystemWindows; + private final Handler mMainHandler; + + private final TvSplitMenuView mSplitMenuView; + + private final ActionBroadcastReceiver mActionBroadcastReceiver; + + private final int mTvButtonFadeAnimationDuration; + + public TvSplitMenuController(Context context, StageController stageController, + SystemWindows systemWindows, Handler mainHandler) { + mContext = context; + mMainHandler = mainHandler; + mStageController = stageController; + mSystemWindows = systemWindows; + + mTvButtonFadeAnimationDuration = context.getResources() + .getInteger(R.integer.tv_window_menu_fade_animation_duration); + + mSplitMenuView = (TvSplitMenuView) LayoutInflater.from(context) + .inflate(R.layout.tv_split_menu_view, null); + mSplitMenuView.setListener(this); + + mActionBroadcastReceiver = new ActionBroadcastReceiver(); + } + + /** + * Adds the menu view for the splitscreen to SystemWindows. + */ + void addSplitMenuViewToSystemWindows() { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + mContext.getResources().getDisplayMetrics().widthPixels, + mContext.getResources().getDisplayMetrics().heightPixels, + TYPE_DOCK_DIVIDER, + FLAG_NOT_TOUCHABLE, + PixelFormat.TRANSLUCENT); + lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; + mSplitMenuView.setAlpha(0); + mSystemWindows.addView(mSplitMenuView, lp, DEFAULT_DISPLAY, SHELL_ROOT_LAYER_DIVIDER); + } + + /** + * Removes the menu view for the splitscreen from SystemWindows. + */ + void removeSplitMenuViewFromSystemWindows() { + mSystemWindows.removeView(mSplitMenuView); + } + + /** + * Registers BroadcastReceiver when split screen mode is entered. + */ + void registerBroadcastReceiver() { + mActionBroadcastReceiver.register(); + } + + /** + * Unregisters BroadcastReceiver when split screen mode is entered. + */ + void unregisterBroadcastReceiver() { + mActionBroadcastReceiver.unregister(); + } + + @Override + public void onBackPress() { + setMenuVisibility(false); + } + + @Override + public void onFocusStage(@SplitScreenConstants.SplitPosition int stageToFocus) { + setMenuVisibility(false); + mStageController.grantFocusToStage(stageToFocus); + } + + @Override + public void onCloseStage(@SplitScreenConstants.SplitPosition int stageToClose) { + setMenuVisibility(false); + mStageController.exitStage(stageToClose); + } + + @Override + public void onSwapPress() { + mStageController.swapStages(); + } + + private void setMenuVisibility(boolean visible) { + applyMenuVisibility(visible); + setMenuFocus(visible); + } + + private void applyMenuVisibility(boolean visible) { + float alphaTarget = visible ? 1F : 0F; + + if (mSplitMenuView.getAlpha() == alphaTarget) { + return; + } + + mSplitMenuView.animate() + .alpha(alphaTarget) + .setDuration(mTvButtonFadeAnimationDuration) + .withStartAction(() -> { + if (alphaTarget != 0) { + mSplitMenuView.setVisibility(VISIBLE); + } + }) + .withEndAction(() -> { + if (alphaTarget == 0) { + mSplitMenuView.setVisibility(INVISIBLE); + } + }); + + } + + private void setMenuFocus(boolean focused) { + try { + WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null, + mSystemWindows.getFocusGrantToken(mSplitMenuView), focused); + } catch (RemoteException e) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "%s: Unable to update focus, %s", TAG, e); + } + } + + interface StageController { + void grantFocusToStage(@SplitScreenConstants.SplitPosition int stageToFocus); + void exitStage(@SplitScreenConstants.SplitPosition int stageToClose); + void swapStages(); + } + + private class ActionBroadcastReceiver extends BroadcastReceiver { + + final IntentFilter mIntentFilter; + { + mIntentFilter = new IntentFilter(); + mIntentFilter.addAction(ACTION_SHOW_MENU); + } + boolean mRegistered = false; + + void register() { + if (mRegistered) return; + + mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION, + mMainHandler); + mRegistered = true; + } + + void unregister() { + if (!mRegistered) return; + + mContext.unregisterReceiver(this); + mRegistered = false; + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + + if (ACTION_SHOW_MENU.equals(action)) { + setMenuVisibility(true); + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java new file mode 100644 index 000000000000..88e9757a9b31 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 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.splitscreen.tv; + +import static android.view.KeyEvent.ACTION_DOWN; +import static android.view.KeyEvent.KEYCODE_BACK; + +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.widget.LinearLayout; + +import androidx.annotation.Nullable; + +import com.android.wm.shell.R; +import com.android.wm.shell.common.split.SplitScreenConstants; + +/** + * A View for the Menu Window. + */ +public class TvSplitMenuView extends LinearLayout implements View.OnClickListener { + + private Listener mListener; + + public TvSplitMenuView(Context context) { + super(context); + } + + public TvSplitMenuView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public TvSplitMenuView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + initButtons(); + } + + @Override + public void onClick(View v) { + if (mListener == null) return; + + final int id = v.getId(); + if (id == R.id.tv_split_main_menu_focus_button) { + mListener.onFocusStage(SPLIT_POSITION_TOP_OR_LEFT); + } else if (id == R.id.tv_split_main_menu_close_button) { + mListener.onCloseStage(SPLIT_POSITION_TOP_OR_LEFT); + } else if (id == R.id.tv_split_side_menu_focus_button) { + mListener.onFocusStage(SPLIT_POSITION_BOTTOM_OR_RIGHT); + } else if (id == R.id.tv_split_side_menu_close_button) { + mListener.onCloseStage(SPLIT_POSITION_BOTTOM_OR_RIGHT); + } else if (id == R.id.tv_split_menu_swap_stages) { + mListener.onSwapPress(); + } + } + + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == ACTION_DOWN) { + if (event.getKeyCode() == KEYCODE_BACK) { + if (mListener != null) { + mListener.onBackPress(); + return true; + } + } + } + return super.dispatchKeyEvent(event); + } + + private void initButtons() { + findViewById(R.id.tv_split_main_menu_focus_button).setOnClickListener(this); + findViewById(R.id.tv_split_main_menu_close_button).setOnClickListener(this); + findViewById(R.id.tv_split_side_menu_focus_button).setOnClickListener(this); + findViewById(R.id.tv_split_side_menu_close_button).setOnClickListener(this); + findViewById(R.id.tv_split_menu_swap_stages).setOnClickListener(this); + } + + void setListener(Listener listener) { + mListener = listener; + } + + interface Listener { + /** "Back" button from the remote control */ + void onBackPress(); + + /** Menu Action Buttons */ + + void onFocusStage(@SplitScreenConstants.SplitPosition int stageToFocus); + + void onCloseStage(@SplitScreenConstants.SplitPosition int stageToClose); + + void onSwapPress(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java new file mode 100644 index 000000000000..46d2a5a11671 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 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.splitscreen.tv; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.content.Context; +import android.os.Handler; + +import com.android.launcher3.icons.IconProvider; +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.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.draganddrop.DragAndDropController; +import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.splitscreen.StageCoordinator; +import com.android.wm.shell.sysui.ShellCommandHandler; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +import java.util.Optional; + +/** + * Class inherits from {@link SplitScreenController} and provides {@link TvStageCoordinator} + * for Split Screen on TV. + */ +public class TvSplitScreenController extends SplitScreenController { + private final ShellTaskOrganizer mTaskOrganizer; + private final SyncTransactionQueue mSyncQueue; + private final Context mContext; + private final ShellExecutor mMainExecutor; + private final DisplayController mDisplayController; + private final DisplayImeController mDisplayImeController; + private final DisplayInsetsController mDisplayInsetsController; + private final Transitions mTransitions; + private final TransactionPool mTransactionPool; + private final IconProvider mIconProvider; + private final Optional<RecentTasksController> mRecentTasksOptional; + + private final Handler mMainHandler; + private final SystemWindows mSystemWindows; + + public TvSplitScreenController(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, + ShellController shellController, + ShellTaskOrganizer shellTaskOrganizer, + SyncTransactionQueue syncQueue, + RootTaskDisplayAreaOrganizer rootTDAOrganizer, + DisplayController displayController, + DisplayImeController displayImeController, + DisplayInsetsController displayInsetsController, + DragAndDropController dragAndDropController, + Transitions transitions, + TransactionPool transactionPool, + IconProvider iconProvider, + Optional<RecentTasksController> recentTasks, + ShellExecutor mainExecutor, + Handler mainHandler, + SystemWindows systemWindows) { + super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer, + syncQueue, rootTDAOrganizer, displayController, displayImeController, + displayInsetsController, dragAndDropController, transitions, transactionPool, + iconProvider, recentTasks, mainExecutor); + + mTaskOrganizer = shellTaskOrganizer; + mSyncQueue = syncQueue; + mContext = context; + mMainExecutor = mainExecutor; + mDisplayController = displayController; + mDisplayImeController = displayImeController; + mDisplayInsetsController = displayInsetsController; + mTransitions = transitions; + mTransactionPool = transactionPool; + mIconProvider = iconProvider; + mRecentTasksOptional = recentTasks; + + mMainHandler = mainHandler; + mSystemWindows = systemWindows; + } + + /** + * Provides Tv-specific StageCoordinator. + * @return {@link TvStageCoordinator} + */ + @Override + protected StageCoordinator createStageCoordinator() { + return new TvStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, + mTaskOrganizer, mDisplayController, mDisplayImeController, + mDisplayInsetsController, mTransitions, mTransactionPool, + mIconProvider, mMainExecutor, mMainHandler, + mRecentTasksOptional, mSystemWindows); + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java new file mode 100644 index 000000000000..4d563fbb7f04 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 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.splitscreen.tv; + +import android.content.Context; +import android.os.Handler; + +import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.split.SplitScreenConstants; +import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.splitscreen.StageCoordinator; +import com.android.wm.shell.transition.Transitions; + +import java.util.Optional; + +/** + * Expands {@link StageCoordinator} functionality with Tv-specific methods. + */ +public class TvStageCoordinator extends StageCoordinator + implements TvSplitMenuController.StageController { + + private final TvSplitMenuController mTvSplitMenuController; + + public TvStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, + ShellTaskOrganizer taskOrganizer, DisplayController displayController, + DisplayImeController displayImeController, + DisplayInsetsController displayInsetsController, Transitions transitions, + TransactionPool transactionPool, + IconProvider iconProvider, ShellExecutor mainExecutor, + Handler mainHandler, + Optional<RecentTasksController> recentTasks, + SystemWindows systemWindows) { + super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController, + displayInsetsController, transitions, transactionPool, iconProvider, + mainExecutor, recentTasks); + + mTvSplitMenuController = new TvSplitMenuController(context, this, + systemWindows, mainHandler); + + } + + @Override + protected void onSplitScreenEnter() { + mTvSplitMenuController.addSplitMenuViewToSystemWindows(); + mTvSplitMenuController.registerBroadcastReceiver(); + } + + @Override + protected void onSplitScreenExit() { + mTvSplitMenuController.unregisterBroadcastReceiver(); + mTvSplitMenuController.removeSplitMenuViewFromSystemWindows(); + } + + @Override + public void grantFocusToStage(@SplitScreenConstants.SplitPosition int stageToFocus) { + super.grantFocusToStage(stageToFocus); + } + + @Override + public void exitStage(@SplitScreenConstants.SplitPosition int stageToClose) { + super.exitStage(stageToClose); + } + + /** + * Swaps the stages inside the SplitLayout. + */ + @Override + public void swapStages() { + onDoubleTappedDivider(); + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 839d56a43222..ebb957b2201b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -16,8 +16,10 @@ package com.android.wm.shell.startingsurface; +import static android.content.Context.CONTEXT_RESTRICTED; import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.view.Display.DEFAULT_DISPLAY; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; @@ -29,6 +31,7 @@ import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.ActivityThread; import android.content.BroadcastReceiver; import android.content.Context; @@ -48,9 +51,11 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; @@ -58,7 +63,9 @@ import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Slog; import android.view.ContextThemeWrapper; +import android.view.Display; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.SplashScreenView; import android.window.StartingWindowInfo; import android.window.StartingWindowInfo.StartingWindowType; @@ -134,6 +141,144 @@ public class SplashscreenContentDrawer { } /** + * Help method to create a layout parameters for a window. + */ + static Context createContext(Context initContext, StartingWindowInfo windowInfo, + int theme, @StartingWindowInfo.StartingWindowType int suggestType, + DisplayManager displayManager) { + final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo; + final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null + ? windowInfo.targetActivityInfo + : taskInfo.topActivityInfo; + if (activityInfo == null || activityInfo.packageName == null) { + return null; + } + + final int displayId = taskInfo.displayId; + final int taskId = taskInfo.taskId; + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d", + activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType); + final Display display = displayManager.getDisplay(displayId); + if (display == null) { + // Can't show splash screen on requested display, so skip showing at all. + return null; + } + Context context = displayId == DEFAULT_DISPLAY + ? initContext : initContext.createDisplayContext(display); + if (context == null) { + return null; + } + if (theme != context.getThemeResId()) { + try { + context = context.createPackageContextAsUser(activityInfo.packageName, + CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId)); + context.setTheme(theme); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Failed creating package context with package name " + + activityInfo.packageName + " for user " + taskInfo.userId, e); + return null; + } + } + + final Configuration taskConfig = taskInfo.getConfiguration(); + if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "addSplashScreen: creating context based on task Configuration %s", + taskConfig); + final Context overrideContext = context.createConfigurationContext(taskConfig); + overrideContext.setTheme(theme); + final TypedArray typedArray = overrideContext.obtainStyledAttributes( + com.android.internal.R.styleable.Window); + final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0); + try { + if (resId != 0 && overrideContext.getDrawable(resId) != null) { + // We want to use the windowBackground for the override context if it is + // available, otherwise we use the default one to make sure a themed starting + // window is displayed for the app. + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "addSplashScreen: apply overrideConfig %s", + taskConfig); + context = overrideContext; + } + } catch (Resources.NotFoundException e) { + Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: " + + taskId, e); + return null; + } + typedArray.recycle(); + } + return context; + } + + /** + * Creates the window layout parameters for splashscreen window. + */ + static WindowManager.LayoutParams createLayoutParameters(Context context, + StartingWindowInfo windowInfo, + @StartingWindowInfo.StartingWindowType int suggestType, + CharSequence title, int pixelFormat, IBinder appToken) { + final WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); + params.setFitInsetsSides(0); + params.setFitInsetsTypes(0); + params.format = pixelFormat; + int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; + final TypedArray a = context.obtainStyledAttributes(R.styleable.Window); + if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) { + windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; + } + if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { + if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) { + windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + } + } else { + windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + } + params.layoutInDisplayCutoutMode = a.getInt( + R.styleable.Window_windowLayoutInDisplayCutoutMode, + params.layoutInDisplayCutoutMode); + params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0); + a.recycle(); + + final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo; + final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null + ? windowInfo.targetActivityInfo + : taskInfo.topActivityInfo; + final int displayId = taskInfo.displayId; + // Assumes it's safe to show starting windows of launched apps while + // the keyguard is being hidden. This is okay because starting windows never show + // secret information. + // TODO(b/113840485): Occluded may not only happen on default display + if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) { + windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; + } + + // Force the window flags: this is a fake window, so it is not really + // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM + // flag because we do know that the next window will take input + // focus, so we want to get the IME window up on top of us right away. + // Touches will only pass through to the host activity window and will be blocked from + // passing to any other windows. + windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + params.flags = windowFlags; + params.token = appToken; + params.packageName = activityInfo.packageName; + params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + + if (!context.getResources().getCompatibilityInfo().supportsScreen()) { + params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; + } + + params.setTitle("Splash Screen " + title); + return params; + } + /** * Create a SplashScreenView object. * * In order to speed up the splash screen view to show on first frame, preparing the @@ -248,6 +393,26 @@ public class SplashscreenContentDrawer { return null; } + /** + * Creates a SplashScreenView without read animatable icon and branding image. + */ + SplashScreenView makeSimpleSplashScreenContentView(Context context, + StartingWindowInfo info, int themeBGColor) { + updateDensity(); + mTmpAttrs.reset(); + final ActivityInfo ai = info.targetActivityInfo != null + ? info.targetActivityInfo + : info.taskInfo.topActivityInfo; + + final SplashViewBuilder builder = new SplashViewBuilder(context, ai); + final SplashScreenView view = builder + .setWindowBGColor(themeBGColor) + .chooseStyle(STARTING_WINDOW_TYPE_SPLASH_SCREEN) + .build(); + view.setNotCopyable(); + return view; + } + private SplashScreenView makeSplashScreenContentView(Context context, StartingWindowInfo info, @StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) { updateDensity(); @@ -263,7 +428,8 @@ public class SplashscreenContentDrawer { final int themeBGColor = legacyDrawable != null ? getBGColorFromCache(ai, () -> estimateWindowBGColor(legacyDrawable)) : getBGColorFromCache(ai, () -> peekWindowBGColor(context, mTmpAttrs)); - return new StartingWindowViewBuilder(context, ai) + + return new SplashViewBuilder(context, ai) .setWindowBGColor(themeBGColor) .overlayDrawable(legacyDrawable) .chooseStyle(suggestType) @@ -322,6 +488,14 @@ public class SplashscreenContentDrawer { private Drawable mSplashScreenIcon = null; private Drawable mBrandingImage = null; private int mIconBgColor = Color.TRANSPARENT; + + void reset() { + mWindowBgResId = 0; + mWindowBgColor = Color.TRANSPARENT; + mSplashScreenIcon = null; + mBrandingImage = null; + mIconBgColor = Color.TRANSPARENT; + } } /** @@ -351,7 +525,7 @@ public class SplashscreenContentDrawer { return appReadyDuration; } - private class StartingWindowViewBuilder { + private class SplashViewBuilder { private final Context mContext; private final ActivityInfo mActivityInfo; @@ -364,27 +538,28 @@ public class SplashscreenContentDrawer { /** @see #setAllowHandleSolidColor(boolean) **/ private boolean mAllowHandleSolidColor; - StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) { + SplashViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) { mContext = context; mActivityInfo = aInfo; } - StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) { + SplashViewBuilder setWindowBGColor(@ColorInt int background) { mThemeColor = background; return this; } - StartingWindowViewBuilder overlayDrawable(Drawable overlay) { + SplashViewBuilder overlayDrawable(Drawable overlay) { mOverlayDrawable = overlay; return this; } - StartingWindowViewBuilder chooseStyle(int suggestType) { + SplashViewBuilder chooseStyle(int suggestType) { mSuggestType = suggestType; return this; } - StartingWindowViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) { + // Set up the UI thread for the View. + SplashViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) { mUiThreadInitTask = uiThreadInitTask; return this; } @@ -395,7 +570,7 @@ public class SplashscreenContentDrawer { * android.window.SplashScreen.OnExitAnimationListener#onSplashScreenExit(SplashScreenView)} * callback, effectively copying the {@link SplashScreenView} into the client process. */ - StartingWindowViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) { + SplashViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) { mAllowHandleSolidColor = allowHandleSolidColor; return this; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java index 7f6bfd23f72b..e419462012e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -62,7 +62,7 @@ public class SplashscreenIconDrawableFactory { */ static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor, @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize, - boolean loadInDetail, Handler splashscreenWorkerHandler) { + boolean loadInDetail, Handler preDrawHandler) { Drawable foreground; Drawable background = null; boolean drawBackground = @@ -74,13 +74,13 @@ public class SplashscreenIconDrawableFactory { // If the icon is Adaptive, we already use the icon background. drawBackground = false; foreground = new ImmobileIconDrawable(foregroundDrawable, - srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler); + srcIconSize, iconSize, loadInDetail, preDrawHandler); } else { // Adaptive icon don't handle transparency so we draw the background of the adaptive // icon with the same color as the window background color instead of using two layers foreground = new ImmobileIconDrawable( new AdaptiveForegroundDrawable(foregroundDrawable), - srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler); + srcIconSize, iconSize, loadInDetail, preDrawHandler); } if (drawBackground) { @@ -91,9 +91,9 @@ public class SplashscreenIconDrawableFactory { } static Drawable[] makeLegacyIconDrawable(@NonNull Drawable iconDrawable, int srcIconSize, - int iconSize, boolean loadInDetail, Handler splashscreenWorkerHandler) { + int iconSize, boolean loadInDetail, Handler preDrawHandler) { return new Drawable[]{new ImmobileIconDrawable(iconDrawable, srcIconSize, iconSize, - loadInDetail, splashscreenWorkerHandler)}; + loadInDetail, preDrawHandler)}; } /** @@ -107,14 +107,14 @@ public class SplashscreenIconDrawableFactory { private Bitmap mIconBitmap; ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize, boolean loadInDetail, - Handler splashscreenWorkerHandler) { + Handler preDrawHandler) { // This icon has lower density, don't scale it. if (loadInDetail) { - splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, iconSize)); + preDrawHandler.post(() -> preDrawIcon(drawable, iconSize)); } else { final float scale = (float) iconSize / srcIconSize; mMatrix.setScale(scale, scale); - splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, srcIconSize)); + preDrawHandler.post(() -> preDrawIcon(drawable, srcIconSize)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 22e804547d5c..4f07bfeacce5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -16,7 +16,6 @@ package com.android.wm.shell.startingsurface; -import static android.content.Context.CONTEXT_RESTRICTED; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION; import static android.view.Display.DEFAULT_DISPLAY; @@ -32,8 +31,6 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PixelFormat; @@ -198,118 +195,21 @@ public class StartingSurfaceDrawer { if (activityInfo == null || activityInfo.packageName == null) { return; } - - final int displayId = taskInfo.displayId; - final int taskId = taskInfo.taskId; - // replace with the default theme if the application didn't set final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, - "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d", - activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType); - final Display display = getDisplay(displayId); - if (display == null) { - // Can't show splash screen on requested display, so skip showing at all. - return; - } - Context context = displayId == DEFAULT_DISPLAY - ? mContext : mContext.createDisplayContext(display); + final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme, + suggestType, mDisplayManager); if (context == null) { return; } - if (theme != context.getThemeResId()) { - try { - context = context.createPackageContextAsUser(activityInfo.packageName, - CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId)); - context.setTheme(theme); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Failed creating package context with package name " - + activityInfo.packageName + " for user " + taskInfo.userId, e); - return; - } - } + final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters( + context, windowInfo, suggestType, activityInfo.packageName, + suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN + ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, appToken); - final Configuration taskConfig = taskInfo.getConfiguration(); - if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, - "addSplashScreen: creating context based on task Configuration %s", - taskConfig); - final Context overrideContext = context.createConfigurationContext(taskConfig); - overrideContext.setTheme(theme); - final TypedArray typedArray = overrideContext.obtainStyledAttributes( - com.android.internal.R.styleable.Window); - final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0); - try { - if (resId != 0 && overrideContext.getDrawable(resId) != null) { - // We want to use the windowBackground for the override context if it is - // available, otherwise we use the default one to make sure a themed starting - // window is displayed for the app. - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, - "addSplashScreen: apply overrideConfig %s", - taskConfig); - context = overrideContext; - } - } catch (Resources.NotFoundException e) { - Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: " - + taskId, e); - return; - } - typedArray.recycle(); - } - - final WindowManager.LayoutParams params = new WindowManager.LayoutParams( - WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); - params.setFitInsetsSides(0); - params.setFitInsetsTypes(0); - params.format = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN - ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; - int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; - final TypedArray a = context.obtainStyledAttributes(R.styleable.Window); - if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) { - windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; - } - if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { - if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) { - windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - } - } else { - windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - } - params.layoutInDisplayCutoutMode = a.getInt( - R.styleable.Window_windowLayoutInDisplayCutoutMode, - params.layoutInDisplayCutoutMode); - params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0); - a.recycle(); - - // Assumes it's safe to show starting windows of launched apps while - // the keyguard is being hidden. This is okay because starting windows never show - // secret information. - // TODO(b/113840485): Occluded may not only happen on default display - if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) { - windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; - } - - // Force the window flags: this is a fake window, so it is not really - // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM - // flag because we do know that the next window will take input - // focus, so we want to get the IME window up on top of us right away. - // Touches will only pass through to the host activity window and will be blocked from - // passing to any other windows. - windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - params.flags = windowFlags; - params.token = appToken; - params.packageName = activityInfo.packageName; - params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - - if (!context.getResources().getCompatibilityInfo().supportsScreen()) { - params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; - } - - params.setTitle("Splash Screen " + activityInfo.packageName); + final int displayId = taskInfo.displayId; + final int taskId = taskInfo.taskId; + final Display display = getDisplay(displayId); // TODO(b/173975965) tracking performance // Prepare the splash screen content view on splash screen worker thread in parallel, so the diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index 7b498e4f54ec..a05ed4f24a08 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -18,50 +18,16 @@ package com.android.wm.shell.startingsurface; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.graphics.Color.WHITE; -import static android.graphics.Color.alpha; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; -import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; -import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; -import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; -import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; -import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; -import static android.view.WindowManager.LayoutParams.FLAG_SCALED; -import static android.view.WindowManager.LayoutParams.FLAG_SECURE; -import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; -import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; -import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; -import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; -import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; -import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; -import static com.android.internal.policy.DecorView.getNavigationBarRect; - import android.annotation.BinderThread; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; -import android.app.ActivityThread; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.GraphicBuffer; -import android.graphics.Matrix; import android.graphics.Paint; -import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.RectF; -import android.hardware.HardwareBuffer; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -73,19 +39,14 @@ import android.view.InputChannel; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.SurfaceControl; -import android.view.SurfaceSession; import android.view.View; -import android.view.ViewGroup; -import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.window.ClientWindowFrames; +import android.window.SnapshotDrawerUtils; import android.window.StartingWindowInfo; import android.window.TaskSnapshot; -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.policy.DecorView; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.view.BaseIWindow; import com.android.wm.shell.common.ShellExecutor; @@ -99,27 +60,8 @@ import java.lang.ref.WeakReference; * @hide */ public class TaskSnapshotWindow { - /** - * When creating the starting window, we use the exact same layout flags such that we end up - * with a window with the exact same dimensions etc. However, these flags are not used in layout - * and might cause other side effects so we exclude them. - */ - static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE - | FLAG_NOT_TOUCHABLE - | FLAG_NOT_TOUCH_MODAL - | FLAG_ALT_FOCUSABLE_IM - | FLAG_NOT_FOCUSABLE - | FLAG_HARDWARE_ACCELERATED - | FLAG_IGNORE_CHEEK_PRESSES - | FLAG_LOCAL_FOCUS_MODE - | FLAG_SLIPPERY - | FLAG_WATCH_OUTSIDE_TOUCH - | FLAG_SPLIT_TOUCH - | FLAG_SCALED - | FLAG_SECURE; - private static final String TAG = StartingWindowController.TAG; - private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s"; + private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId="; private static final long DELAY_REMOVAL_TIME_GENERAL = 100; /** @@ -132,25 +74,12 @@ public class TaskSnapshotWindow { private final Window mWindow; private final Runnable mClearWindowHandler; private final ShellExecutor mSplashScreenExecutor; - private final SurfaceControl mSurfaceControl; private final IWindowSession mSession; - private final Rect mTaskBounds; - private final Rect mFrame = new Rect(); - private final Rect mSystemBarInsets = new Rect(); - private TaskSnapshot mSnapshot; - private final RectF mTmpSnapshotSize = new RectF(); - private final RectF mTmpDstFrame = new RectF(); - private final CharSequence mTitle; private boolean mHasDrawn; - private boolean mSizeMismatch; private final Paint mBackgroundPaint = new Paint(); private final int mActivityType; - private final int mStatusBarColor; - private final SystemBarBackgroundPainter mSystemBarBackgroundPainter; private final int mOrientationOnCreation; - private final SurfaceControl.Transaction mTransaction; - private final Matrix mSnapshotMatrix = new Matrix(); - private final float[] mTmpFloat9 = new float[9]; + private final Runnable mScheduledRunnable = this::removeImmediately; private final boolean mHasImeSurface; @@ -162,42 +91,15 @@ public class TaskSnapshotWindow { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId); - final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams; - final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams; final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; - if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) { - Slog.w(TAG, "unable to create taskSnapshot surface for task: " + taskId); + + final WindowManager.LayoutParams layoutParams = SnapshotDrawerUtils.createLayoutParameters( + info, TITLE_FORMAT + taskId, TYPE_APPLICATION_STARTING, + snapshot.getHardwareBuffer().getFormat(), appToken); + if (layoutParams == null) { + Slog.e(TAG, "TaskSnapshotWindow no layoutParams"); return null; } - final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); - - final int appearance = attrs.insetsFlags.appearance; - final int windowFlags = attrs.flags; - final int windowPrivateFlags = attrs.privateFlags; - - layoutParams.packageName = mainWindowParams.packageName; - layoutParams.windowAnimations = mainWindowParams.windowAnimations; - layoutParams.dimAmount = mainWindowParams.dimAmount; - layoutParams.type = TYPE_APPLICATION_STARTING; - layoutParams.format = snapshot.getHardwareBuffer().getFormat(); - layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES) - | FLAG_NOT_FOCUSABLE - | FLAG_NOT_TOUCHABLE; - // Setting as trusted overlay to let touches pass through. This is safe because this - // window is controlled by the system. - layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) - | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST; - layoutParams.token = appToken; - layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.insetsFlags.appearance = appearance; - layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior; - layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode; - layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes()); - layoutParams.setFitInsetsSides(attrs.getFitInsetsSides()); - layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility()); - - layoutParams.setTitle(String.format(TITLE_FORMAT, taskId)); final Point taskSize = snapshot.getTaskSize(); final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y); @@ -209,7 +111,7 @@ public class TaskSnapshotWindow { final SurfaceControl surfaceControl = new SurfaceControl(); final ClientWindowFrames tmpFrames = new ClientWindowFrames(); - final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0]; + final InsetsSourceControl.Array tmpControls = new InsetsSourceControl.Array(); final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); final TaskDescription taskDescription; @@ -221,9 +123,8 @@ public class TaskSnapshotWindow { } final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow( - surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance, - windowFlags, windowPrivateFlags, taskBounds, orientation, activityType, - topWindowInsetsState, clearWindowHandler, splashScreenExecutor); + snapshot, taskDescription, orientation, activityType, + clearWindowHandler, splashScreenExecutor); final Window window = snapshotSurface.mWindow; final InsetsState tmpInsetsState = new InsetsState(); @@ -233,7 +134,7 @@ public class TaskSnapshotWindow { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay"); final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId, - info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls, + info.requestedVisibleTypes, tmpInputChannel, tmpInsetsState, tmpControls, new Rect(), sizeCompatScale); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (res < 0) { @@ -254,33 +155,25 @@ public class TaskSnapshotWindow { snapshotSurface.clearWindowSynced(); } - final Rect systemBarInsets = getSystemBarInsets(tmpFrames.frame, topWindowInsetsState); - snapshotSurface.setFrames(tmpFrames.frame, systemBarInsets); - snapshotSurface.drawSnapshot(); + SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot, + taskBounds, tmpFrames.frame, topWindowInsetsState, true /* releaseAfterDraw */); + snapshotSurface.mHasDrawn = true; + snapshotSurface.reportDrawn(); + return snapshotSurface; } - public TaskSnapshotWindow(SurfaceControl surfaceControl, - TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription, - int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds, - int currentOrientation, int activityType, InsetsState topWindowInsetsState, - Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) { + public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription, + int currentOrientation, int activityType, Runnable clearWindowHandler, + ShellExecutor splashScreenExecutor) { mSplashScreenExecutor = splashScreenExecutor; mSession = WindowManagerGlobal.getWindowSession(); mWindow = new Window(); mWindow.setSession(mSession); - mSurfaceControl = surfaceControl; - mSnapshot = snapshot; - mTitle = title; int backgroundColor = taskDescription.getBackgroundColor(); mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE); - mTaskBounds = taskBounds; - mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags, - windowPrivateFlags, appearance, taskDescription, 1f, topWindowInsetsState); - mStatusBarColor = taskDescription.getStatusBarColor(); mOrientationOnCreation = currentOrientation; mActivityType = activityType; - mTransaction = new SurfaceControl.Transaction(); mClearWindowHandler = clearWindowHandler; mHasImeSurface = snapshot.hasImeSurface(); } @@ -293,23 +186,6 @@ public class TaskSnapshotWindow { return mHasImeSurface; } - /** - * Ask system bar background painter to draw status bar background. - * @hide - */ - public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) { - mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame, - mSystemBarBackgroundPainter.getStatusBarColorViewHeight()); - } - - /** - * Ask system bar background painter to draw navigation bar background. - * @hide - */ - public void drawNavigationBarBackground(Canvas c) { - mSystemBarBackgroundPainter.drawNavigationBarBackground(c); - } - void scheduleRemove(boolean deferRemoveForIme) { // Show the latest content as soon as possible for unlocking to home. if (mActivityType == ACTIVITY_TYPE_HOME) { @@ -337,178 +213,6 @@ public class TaskSnapshotWindow { } /** - * Set frame size. - * @hide - */ - public void setFrames(Rect frame, Rect systemBarInsets) { - mFrame.set(frame); - mSystemBarInsets.set(systemBarInsets); - final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); - mSizeMismatch = (mFrame.width() != snapshot.getWidth() - || mFrame.height() != snapshot.getHeight()); - mSystemBarBackgroundPainter.setInsets(systemBarInsets); - } - - static Rect getSystemBarInsets(Rect frame, InsetsState state) { - return state.calculateInsets(frame, WindowInsets.Type.systemBars(), - false /* ignoreVisibility */).toRect(); - } - - private void drawSnapshot() { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, - "Drawing snapshot surface sizeMismatch=%b", mSizeMismatch); - if (mSizeMismatch) { - // The dimensions of the buffer and the window don't match, so attaching the buffer - // will fail. Better create a child window with the exact dimensions and fill the parent - // window with the background color! - drawSizeMismatchSnapshot(); - } else { - drawSizeMatchSnapshot(); - } - mHasDrawn = true; - reportDrawn(); - - // In case window manager leaks us, make sure we don't retain the snapshot. - if (mSnapshot.getHardwareBuffer() != null) { - mSnapshot.getHardwareBuffer().close(); - } - mSnapshot = null; - mSurfaceControl.release(); - } - - private void drawSizeMatchSnapshot() { - mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer()) - .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace()) - .apply(); - } - - private void drawSizeMismatchSnapshot() { - final HardwareBuffer buffer = mSnapshot.getHardwareBuffer(); - final SurfaceSession session = new SurfaceSession(); - - // We consider nearly matched dimensions as there can be rounding errors and the user won't - // notice very minute differences from scaling one dimension more than the other - final boolean aspectRatioMismatch = Math.abs( - ((float) buffer.getWidth() / buffer.getHeight()) - - ((float) mFrame.width() / mFrame.height())) > 0.01f; - - // Keep a reference to it such that it doesn't get destroyed when finalized. - SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session) - .setName(mTitle + " - task-snapshot-surface") - .setBLASTLayer() - .setFormat(buffer.getFormat()) - .setParent(mSurfaceControl) - .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot") - .build(); - - final Rect frame; - // We can just show the surface here as it will still be hidden as the parent is - // still hidden. - mTransaction.show(childSurfaceControl); - if (aspectRatioMismatch) { - // Clip off ugly navigation bar. - final Rect crop = calculateSnapshotCrop(); - frame = calculateSnapshotFrame(crop); - mTransaction.setWindowCrop(childSurfaceControl, crop); - mTransaction.setPosition(childSurfaceControl, frame.left, frame.top); - mTmpSnapshotSize.set(crop); - mTmpDstFrame.set(frame); - } else { - frame = null; - mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight()); - mTmpDstFrame.set(mFrame); - mTmpDstFrame.offsetTo(0, 0); - } - - // Scale the mismatch dimensions to fill the task bounds - mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL); - mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9); - mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace()); - mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer()); - - if (aspectRatioMismatch) { - GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(), - PixelFormat.RGBA_8888, - GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER - | GraphicBuffer.USAGE_SW_WRITE_RARELY); - // TODO: Support this on HardwareBuffer - final Canvas c = background.lockCanvas(); - drawBackgroundAndBars(c, frame); - background.unlockCanvasAndPost(c); - mTransaction.setBuffer(mSurfaceControl, - HardwareBuffer.createFromGraphicBuffer(background)); - } - mTransaction.apply(); - childSurfaceControl.release(); - } - - /** - * Calculates the snapshot crop in snapshot coordinate space. - * - * @return crop rect in snapshot coordinate space. - */ - public Rect calculateSnapshotCrop() { - final Rect rect = new Rect(); - final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); - rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight()); - final Rect insets = mSnapshot.getContentInsets(); - - final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; - final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; - - // Let's remove all system decorations except the status bar, but only if the task is at the - // very top of the screen. - final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0; - rect.inset((int) (insets.left * scaleX), - isTop ? 0 : (int) (insets.top * scaleY), - (int) (insets.right * scaleX), - (int) (insets.bottom * scaleY)); - return rect; - } - - /** - * Calculates the snapshot frame in window coordinate space from crop. - * - * @param crop rect that is in snapshot coordinate space. - */ - public Rect calculateSnapshotFrame(Rect crop) { - final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); - final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; - final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; - - // Rescale the frame from snapshot to window coordinate space - final Rect frame = new Rect(0, 0, - (int) (crop.width() / scaleX + 0.5f), - (int) (crop.height() / scaleY + 0.5f) - ); - - // However, we also need to make space for the navigation bar on the left side. - frame.offset(mSystemBarInsets.left, 0); - return frame; - } - - /** - * Draw status bar and navigation bar background. - * @hide - */ - public void drawBackgroundAndBars(Canvas c, Rect frame) { - final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight(); - final boolean fillHorizontally = c.getWidth() > frame.right; - final boolean fillVertically = c.getHeight() > frame.bottom; - if (fillHorizontally) { - c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0, - c.getWidth(), fillVertically - ? frame.bottom - : c.getHeight(), - mBackgroundPaint); - } - if (fillVertically) { - c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint); - } - mSystemBarBackgroundPainter.drawDecors(c, frame); - } - - /** * Clear window from drawer, must be post on main executor. */ private void clearWindowSynced() { @@ -535,7 +239,7 @@ public class TaskSnapshotWindow { public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId, - int resizeMode) { + boolean dragResizing) { final TaskSnapshotWindow snapshot = mOuter.get(); if (snapshot == null) { return; @@ -556,91 +260,4 @@ public class TaskSnapshotWindow { }); } } - - /** - * Helper class to draw the background of the system bars in regions the task snapshot isn't - * filling the window. - */ - static class SystemBarBackgroundPainter { - private final Paint mStatusBarPaint = new Paint(); - private final Paint mNavigationBarPaint = new Paint(); - private final int mStatusBarColor; - private final int mNavigationBarColor; - private final int mWindowFlags; - private final int mWindowPrivateFlags; - private final float mScale; - private final InsetsState mInsetsState; - private final Rect mSystemBarInsets = new Rect(); - - SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance, - TaskDescription taskDescription, float scale, InsetsState insetsState) { - mWindowFlags = windowFlags; - mWindowPrivateFlags = windowPrivateFlags; - mScale = scale; - final Context context = ActivityThread.currentActivityThread().getSystemUiContext(); - final int semiTransparent = context.getColor( - R.color.system_bar_background_semi_transparent); - mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS, - semiTransparent, taskDescription.getStatusBarColor(), appearance, - APPEARANCE_LIGHT_STATUS_BARS, - taskDescription.getEnsureStatusBarContrastWhenTransparent()); - mNavigationBarColor = DecorView.calculateBarColor(windowFlags, - FLAG_TRANSLUCENT_NAVIGATION, semiTransparent, - taskDescription.getNavigationBarColor(), appearance, - APPEARANCE_LIGHT_NAVIGATION_BARS, - taskDescription.getEnsureNavigationBarContrastWhenTransparent() - && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim)); - mStatusBarPaint.setColor(mStatusBarColor); - mNavigationBarPaint.setColor(mNavigationBarColor); - mInsetsState = insetsState; - } - - void setInsets(Rect systemBarInsets) { - mSystemBarInsets.set(systemBarInsets); - } - - int getStatusBarColorViewHeight() { - final boolean forceBarBackground = - (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; - if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( - mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) { - return (int) (mSystemBarInsets.top * mScale); - } else { - return 0; - } - } - - private boolean isNavigationBarColorViewVisible() { - final boolean forceBarBackground = - (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; - return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( - mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground); - } - - void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) { - drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight()); - drawNavigationBarBackground(c); - } - - void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, - int statusBarHeight) { - if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0 - && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) { - final int rightInset = (int) (mSystemBarInsets.right * mScale); - final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0; - c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint); - } - } - - @VisibleForTesting - void drawNavigationBarBackground(Canvas c) { - final Rect navigationBarRect = new Rect(); - getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect, - mScale); - final boolean visible = isNavigationBarColorViewVisible(); - if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) { - c.drawRect(navigationBarRect, mNavigationBarPaint); - } - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java index 74fe8fbbd5e0..5c455270d9b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java @@ -16,7 +16,7 @@ package com.android.wm.shell.startingsurface.tv; -import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN; +import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE; import android.window.StartingWindowInfo; @@ -30,6 +30,6 @@ public class TvStartingWindowTypeAlgorithm implements StartingWindowTypeAlgorith @Override public int getSuggestedWindowType(StartingWindowInfo windowInfo) { // For now we want to always show empty splash screens on TV. - return STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN; + return STARTING_WINDOW_TYPE_NONE; } } 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 928e71f8d3a6..618c4465db3b 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 @@ -41,6 +41,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; @@ -300,6 +301,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return true; } + // check if no-animation and skip animation if so. + if (Transitions.isAllNoAnimation(info)) { + startTransaction.apply(); + finishTransaction.apply(); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + return true; + } + if (mAnimations.containsKey(transition)) { throw new IllegalStateException("Got a duplicate startAnimation call for " + transition); @@ -395,6 +404,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } + // The back gesture has animated this change before transition happen, so here we don't + // play the animation again. + if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) { + continue; + } // Don't animate anything that isn't independent. if (!TransitionInfo.isIndependent(change, info)) continue; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl index bdcdb63d2cd6..cc4d268a0000 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl @@ -34,4 +34,9 @@ interface IShellTransitions { * Unregisters a remote transition handler. */ oneway void unregisterRemote(in RemoteTransition remoteTransition) = 2; + + /** + * Retrieves the apply-token used by transactions in Shell + */ + IBinder getShellApplyToken() = 3; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index b647f43da522..66d0a2aa409b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -16,8 +16,6 @@ package com.android.wm.shell.transition; -import static android.hardware.HardwareBuffer.RGBA_8888; -import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT; import static android.util.RotationUtils.deltaRotation; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT; @@ -37,8 +35,6 @@ import android.graphics.ColorSpace; import android.graphics.Matrix; import android.graphics.Rect; import android.hardware.HardwareBuffer; -import android.media.Image; -import android.media.ImageReader; import android.util.Slog; import android.view.Surface; import android.view.SurfaceControl; @@ -46,15 +42,15 @@ import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.window.ScreenCapture; import android.window.TransitionInfo; import com.android.internal.R; +import com.android.internal.policy.TransitionAnimation; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; -import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; /** * This class handles the rotation animation when the device is rotated. @@ -144,14 +140,14 @@ class ScreenRotationAnimation { t.reparent(mScreenshotLayer, mAnimLeash); mStartLuma = change.getSnapshotLuma(); } else { - SurfaceControl.LayerCaptureArgs args = - new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl) + ScreenCapture.LayerCaptureArgs args = + new ScreenCapture.LayerCaptureArgs.Builder(mSurfaceControl) .setCaptureSecureLayers(true) .setAllowProtected(true) .setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight)) .build(); - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = - SurfaceControl.captureLayers(args); + ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = + ScreenCapture.captureLayers(args); if (screenshotBuffer == null) { Slog.w(TAG, "Unable to take screenshot of display"); return; @@ -172,7 +168,7 @@ class ScreenRotationAnimation { t.setBuffer(mScreenshotLayer, hardwareBuffer); t.show(mScreenshotLayer); if (!isCustomRotate()) { - mStartLuma = getMedianBorderLuma(hardwareBuffer, colorSpace); + mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, colorSpace); } } @@ -403,93 +399,6 @@ class ScreenRotationAnimation { mTransactionPool.release(t); } - /** - * Converts the provided {@link HardwareBuffer} and converts it to a bitmap to then sample the - * luminance at the borders of the bitmap - * @return the average luminance of all the pixels at the borders of the bitmap - */ - private static float getMedianBorderLuma(HardwareBuffer hardwareBuffer, ColorSpace colorSpace) { - // Cannot read content from buffer with protected usage. - if (hardwareBuffer == null || hardwareBuffer.getFormat() != RGBA_8888 - || hasProtectedContent(hardwareBuffer)) { - return 0; - } - - ImageReader ir = ImageReader.newInstance(hardwareBuffer.getWidth(), - hardwareBuffer.getHeight(), hardwareBuffer.getFormat(), 1); - ir.getSurface().attachAndQueueBufferWithColorSpace(hardwareBuffer, colorSpace); - Image image = ir.acquireLatestImage(); - if (image == null || image.getPlanes().length == 0) { - return 0; - } - - Image.Plane plane = image.getPlanes()[0]; - ByteBuffer buffer = plane.getBuffer(); - int width = image.getWidth(); - int height = image.getHeight(); - int pixelStride = plane.getPixelStride(); - int rowStride = plane.getRowStride(); - float[] borderLumas = new float[2 * width + 2 * height]; - - // Grab the top and bottom borders - int l = 0; - for (int x = 0; x < width; x++) { - borderLumas[l++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride); - borderLumas[l++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride); - } - - // Grab the left and right borders - for (int y = 0; y < height; y++) { - borderLumas[l++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride); - borderLumas[l++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride); - } - - // Cleanup - ir.close(); - - // Oh, is this too simple and inefficient for you? - // How about implementing a O(n) solution? https://en.wikipedia.org/wiki/Median_of_medians - Arrays.sort(borderLumas); - return borderLumas[borderLumas.length / 2]; - } - - /** - * @return whether the hardwareBuffer passed in is marked as protected. - */ - private static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) { - return (hardwareBuffer.getUsage() & USAGE_PROTECTED_CONTENT) == USAGE_PROTECTED_CONTENT; - } - - private static float getPixelLuminance(ByteBuffer buffer, int x, int y, - int pixelStride, int rowStride) { - int offset = y * rowStride + x * pixelStride; - int pixel = 0; - pixel |= (buffer.get(offset) & 0xff) << 16; // R - pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G - pixel |= (buffer.get(offset + 2) & 0xff); // B - pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A - return Color.valueOf(pixel).luminance(); - } - - /** - * Gets the average border luma by taking a screenshot of the {@param surfaceControl}. - * @see #getMedianBorderLuma(HardwareBuffer, ColorSpace) - */ - private static float getLumaOfSurfaceControl(Rect bounds, SurfaceControl surfaceControl) { - if (surfaceControl == null) { - return 0; - } - - Rect crop = new Rect(0, 0, bounds.width(), bounds.height()); - SurfaceControl.ScreenshotHardwareBuffer buffer = - SurfaceControl.captureLayers(surfaceControl, crop, 1); - if (buffer == null) { - return 0; - } - - return getMedianBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace()); - } - private static void applyColor(int startColor, int endColor, float[] rgbFloat, float fraction, SurfaceControl surface, SurfaceControl.Transaction t) { final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index ab792ee122c7..6af81f1eb707 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -48,6 +48,7 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.animation.Animation; import android.view.animation.Transformation; +import android.window.ScreenCapture; import android.window.TransitionInfo; import com.android.internal.R; @@ -314,8 +315,8 @@ public class TransitionAnimationHelper { .setBufferSize(extensionRect.width(), extensionRect.height()) .build(); - final SurfaceControl.LayerCaptureArgs captureArgs = - new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend) + final ScreenCapture.LayerCaptureArgs captureArgs = + new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend) .setSourceCrop(edgeBounds) .setFrameScale(1) .setPixelFormat(PixelFormat.RGBA_8888) @@ -323,8 +324,8 @@ public class TransitionAnimationHelper { .setAllowProtected(true) .setCaptureSecureLayers(true) .build(); - final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer = - SurfaceControl.captureLayers(captureArgs); + final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer = + ScreenCapture.captureLayers(captureArgs); if (edgeBuffer == null) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 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 39fe4559c88f..fc2a828fb263 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 @@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import static android.window.TransitionInfo.FLAG_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; @@ -174,6 +175,9 @@ public class Transitions implements RemoteCallable<Transitions> { } private void onInit() { + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mOrganizer.shareTransactionQueue(); + } mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS, this::createExternalInterface, this); @@ -443,6 +447,34 @@ public class Transitions implements RemoteCallable<Transitions> { return -1; } + /** + * Look through a transition and see if all non-closing changes are no-animation. If so, no + * animation should play. + */ + static boolean isAllNoAnimation(TransitionInfo info) { + if (isClosingType(info.getType())) { + // no-animation is only relevant for launching (open) activities. + return false; + } + boolean hasNoAnimation = false; + final int changeSize = info.getChanges().size(); + for (int i = changeSize - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (isClosingType(change.getMode())) { + // ignore closing apps since they are a side-effect of the transition and don't + // animate. + continue; + } + if (change.hasFlags(FLAG_NO_ANIMATION)) { + hasNoAnimation = true; + } else { + // at-least one relevant participant *is* animated, so we need to animate. + return false; + } + } + return hasNoAnimation; + } + @VisibleForTesting void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { @@ -1016,6 +1048,11 @@ public class Transitions implements RemoteCallable<Transitions> { transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition); }); } + + @Override + public IBinder getShellApplyToken() { + return SurfaceControl.Transaction.getDefaultApplyToken(); + } } private class SettingsObserver extends ContentObserver { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index d3f1332f6224..bb671454e938 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -105,6 +105,7 @@ class DragResizeInputListener implements AutoCloseable { FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, TYPE_APPLICATION, + null /* windowToken */, mFocusGrantToken, TAG + " of " + decorationSurface.toString(), mInputChannel); diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index 3ca5b9c38aff..d6adaa7d533b 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -48,6 +48,6 @@ android_test { "wm-flicker-common-assertions", "wm-flicker-common-app-helpers", "platform-test-annotations", - "wmshell-flicker-test-components", + "flickertestapplib", ], } diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml index 574a9f4da627..27fc381a10d1 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml @@ -13,13 +13,19 @@ <option name="run-command" value="cmd window tracing level all" /> <!-- set WM tracing to frame (avoid incomplete states) --> <option name="run-command" value="cmd window tracing frame" /> + <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests --> + <option name="run-command" value="pm disable com.google.android.internal.betterbug" /> + <!-- ensure lock screen mode is swipe --> + <option name="run-command" value="locksettings set-disabled false" /> <!-- restart launcher to activate TAPL --> <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" /> + <!-- Ensure output directory is empty at the start --> + <option name="run-command" value="rm -rf /sdcard/flicker" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> <option name="test-file-name" value="WMShellFlickerTests.apk"/> - <option name="test-file-name" value="WMShellFlickerTestApp.apk" /> + <option name="test-file-name" value="FlickerTestApp.apk" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> <option name="package" value="com.android.wm.shell.flicker"/> @@ -32,4 +38,4 @@ <option name="collect-on-run-ended-only" value="true" /> <option name="clean-up" value="true" /> </metrics_collector> -</configuration>
\ No newline at end of file +</configuration> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt new file mode 100644 index 000000000000..122c18d41dee --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2022 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.app.Instrumentation +import android.platform.test.annotations.Presubmit +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.entireScreenCovered +import com.android.server.wm.flicker.junit.FlickerBuilderProvider +import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd +import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd +import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd +import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd +import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd +import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible +import com.android.server.wm.traces.common.ComponentNameMatcher +import org.junit.Assume +import org.junit.Test + +/** + * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR], + * [ComponentNameMatcher.TASK_BAR], [ComponentNameMatcher.STATUS_BAR], and general assertions + * (layers visible in consecutive states, entire screen covered, etc.) + */ +abstract class BaseTest +@JvmOverloads +constructor( + protected val flicker: FlickerTest, + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(), + protected val tapl: LauncherInstrumentation = LauncherInstrumentation() +) { + /** Specification of the test transition to execute */ + abstract val transition: FlickerBuilder.() -> Unit + + /** + * Entry point for the test runner. It will use this method to initialize and cache flicker + * executions + */ + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { flicker.scenario.setIsTablet(tapl.isTablet) } + transition() + } + } + + /** Checks that all parts of the screen are covered during the transition */ + @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered() + + /** + * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition + */ + @Presubmit + @Test + open fun navBarLayerIsVisibleAtStartAndEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarLayerIsVisibleAtStartAndEnd() + } + + /** + * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the + * transition + */ + @Presubmit + @Test + open fun navBarLayerPositionAtStartAndEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarLayerPositionAtStartAndEnd() + } + + /** + * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition + * + * Note: Phones only + */ + @Presubmit + @Test + open fun navBarWindowIsAlwaysVisible() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarWindowIsAlwaysVisible() + } + + /** + * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition + */ + @Presubmit + @Test + open fun taskBarLayerIsVisibleAtStartAndEnd() { + Assume.assumeTrue(flicker.scenario.isTablet) + flicker.taskBarLayerIsVisibleAtStartAndEnd() + } + + /** + * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition + * + * Note: Large screen only + */ + @Presubmit + @Test + open fun taskBarWindowIsAlwaysVisible() { + Assume.assumeTrue(flicker.scenario.isTablet) + flicker.taskBarWindowIsAlwaysVisible() + } + + /** + * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole + * transition + */ + @Presubmit + @Test + open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd() + + /** + * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the + * transition + */ + @Presubmit + @Test + open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd() + + /** + * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole + * transition + */ + @Presubmit + @Test + open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible() + + /** + * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive + * entries. + */ + @Presubmit + @Test + open fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() } + } + + /** + * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive + * entries. + */ + @Presubmit + @Test + open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index cba396a82a87..51869140e8fa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -15,27 +15,26 @@ */ @file:JvmName("CommonAssertions") + package com.android.wm.shell.flicker -import android.view.Surface -import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTest import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject +import com.android.server.wm.flicker.traces.layers.LayersTraceSubject +import com.android.server.wm.traces.common.IComponentMatcher import com.android.server.wm.traces.common.region.Region +import com.android.server.wm.traces.common.service.PlatformConsts -fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() { - assertLayersEnd { - this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) - } +fun FlickerTest.appPairsDividerIsVisibleAtEnd() { + assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) } } -fun FlickerTestParameter.appPairsDividerIsInvisibleAtEnd() { - assertLayersEnd { - this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) - } +fun FlickerTest.appPairsDividerIsInvisibleAtEnd() { + assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) } } -fun FlickerTestParameter.appPairsDividerBecomesVisible() { +fun FlickerTest.appPairsDividerBecomesVisible() { assertLayers { this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() @@ -43,91 +42,311 @@ fun FlickerTestParameter.appPairsDividerBecomesVisible() { } } -fun FlickerTestParameter.splitScreenDividerBecomesVisible() { - layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) +fun FlickerTest.splitScreenEntered( + component1: IComponentMatcher, + component2: IComponentMatcher, + fromOtherApp: Boolean, + appExistAtStart: Boolean = true +) { + if (fromOtherApp) { + if (appExistAtStart) { + appWindowIsInvisibleAtStart(component1) + } else { + appWindowIsNotContainAtStart(component1) + } + } else { + appWindowIsVisibleAtStart(component1) + } + if (appExistAtStart) { + appWindowIsInvisibleAtStart(component2) + } else { + appWindowIsNotContainAtStart(component2) + } + splitScreenDividerIsInvisibleAtStart() + + appWindowIsVisibleAtEnd(component1) + appWindowIsVisibleAtEnd(component2) + splitScreenDividerIsVisibleAtEnd() } -fun FlickerTestParameter.layerBecomesVisible( - component: FlickerComponentName +fun FlickerTest.splitScreenDismissed( + component1: IComponentMatcher, + component2: IComponentMatcher, + toHome: Boolean ) { + appWindowIsVisibleAtStart(component1) + appWindowIsVisibleAtStart(component2) + splitScreenDividerIsVisibleAtStart() + + appWindowIsInvisibleAtEnd(component1) + if (toHome) { + appWindowIsInvisibleAtEnd(component2) + } else { + appWindowIsVisibleAtEnd(component2) + } + splitScreenDividerIsInvisibleAtEnd() +} + +fun FlickerTest.splitScreenDividerIsVisibleAtStart() { + assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } +} + +fun FlickerTest.splitScreenDividerIsVisibleAtEnd() { + assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } +} + +fun FlickerTest.splitScreenDividerIsInvisibleAtStart() { + assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } +} + +fun FlickerTest.splitScreenDividerIsInvisibleAtEnd() { + assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } +} + +fun FlickerTest.splitScreenDividerBecomesVisible() { + layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) +} + +fun FlickerTest.splitScreenDividerBecomesInvisible() { assertLayers { - this.isInvisible(component) + this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) .then() - .isVisible(component) + .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } } -fun FlickerTestParameter.layerIsVisibleAtEnd( - component: FlickerComponentName +fun FlickerTest.layerBecomesVisible(component: IComponentMatcher) { + assertLayers { this.isInvisible(component).then().isVisible(component) } +} + +fun FlickerTest.layerBecomesInvisible(component: IComponentMatcher) { + assertLayers { this.isVisible(component).then().isInvisible(component) } +} + +fun FlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) { + assertLayersEnd { this.isVisible(component) } +} + +fun FlickerTest.layerKeepVisible(component: IComponentMatcher) { + assertLayers { this.isVisible(component) } +} + +fun FlickerTest.splitAppLayerBoundsBecomesVisible( + component: IComponentMatcher, + landscapePosLeft: Boolean, + portraitPosTop: Boolean ) { - assertLayersEnd { - this.isVisible(component) + assertLayers { + this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) + .then() + .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) + .then() + .splitAppLayerBoundsSnapToDivider( + component, + landscapePosLeft, + portraitPosTop, + scenario.endRotation + ) + } +} + +fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) { + assertLayers { + this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true) + .then() + .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) + .then() + // TODO(b/245472831): Verify the component should snap to divider. + .isVisible(component) } } -fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible( - rotation: Int, - component: FlickerComponentName, - splitLeftTop: Boolean +fun FlickerTest.splitAppLayerBoundsBecomesInvisible( + component: IComponentMatcher, + landscapePosLeft: Boolean, + portraitPosTop: Boolean ) { assertLayers { - val dividerRegion = this.last().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region - this.isInvisible(component) + this.splitAppLayerBoundsSnapToDivider( + component, + landscapePosLeft, + portraitPosTop, + scenario.endRotation + ) .then() - .invoke("splitAppLayerBoundsBecomesVisible") { - it.visibleRegion(component).overlaps( - if (splitLeftTop) { - getSplitLeftTopRegion(dividerRegion, rotation) - } else { - getSplitRightBottomRegion(dividerRegion, rotation) - } - ) - } + .isVisible(component, true) + .then() + .isInvisible(component) } } -fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd( - rotation: Int, - component: FlickerComponentName, - splitLeftTop: Boolean +fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd( + component: IComponentMatcher, + landscapePosLeft: Boolean, + portraitPosTop: Boolean ) { assertLayersEnd { - val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region - visibleRegion(component).overlaps( - if (splitLeftTop) { - getSplitLeftTopRegion(dividerRegion, rotation) - } else { - getSplitRightBottomRegion(dividerRegion, rotation) - } + splitAppLayerBoundsSnapToDivider( + component, + landscapePosLeft, + portraitPosTop, + scenario.endRotation + ) + } +} + +fun FlickerTest.splitAppLayerBoundsKeepVisible( + component: IComponentMatcher, + landscapePosLeft: Boolean, + portraitPosTop: Boolean +) { + assertLayers { + splitAppLayerBoundsSnapToDivider( + component, + landscapePosLeft, + portraitPosTop, + scenario.endRotation ) } } -fun FlickerTestParameter.appWindowBecomesVisible( - component: FlickerComponentName +fun FlickerTest.splitAppLayerBoundsChanges( + component: IComponentMatcher, + landscapePosLeft: Boolean, + portraitPosTop: Boolean ) { + assertLayers { + if (landscapePosLeft) { + this.splitAppLayerBoundsSnapToDivider( + component, + landscapePosLeft, + portraitPosTop, + scenario.endRotation + ) + } else { + this.splitAppLayerBoundsSnapToDivider( + component, + landscapePosLeft, + portraitPosTop, + scenario.endRotation + ) + .then() + .isInvisible(component) + .then() + .splitAppLayerBoundsSnapToDivider( + component, + landscapePosLeft, + portraitPosTop, + scenario.endRotation + ) + } + } +} + +fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider( + component: IComponentMatcher, + landscapePosLeft: Boolean, + portraitPosTop: Boolean, + rotation: PlatformConsts.Rotation +): LayersTraceSubject { + return invoke("splitAppLayerBoundsSnapToDivider") { + it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation) + } +} + +fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( + component: IComponentMatcher, + landscapePosLeft: Boolean, + portraitPosTop: Boolean, + rotation: PlatformConsts.Rotation +): LayerTraceEntrySubject { + val displayBounds = WindowUtils.getDisplayBounds(rotation) + return invoke { + val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region + visibleRegion(component) + .coversAtMost( + if (displayBounds.width > displayBounds.height) { + if (landscapePosLeft) { + Region.from( + 0, + 0, + (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2, + displayBounds.bounds.bottom + ) + } else { + Region.from( + (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2, + 0, + displayBounds.bounds.right, + displayBounds.bounds.bottom + ) + } + } else { + if (portraitPosTop) { + Region.from( + 0, + 0, + displayBounds.bounds.right, + (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2 + ) + } else { + Region.from( + 0, + (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2, + displayBounds.bounds.right, + displayBounds.bounds.bottom + ) + } + } + ) + } +} + +fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) { assertWm { this.isAppWindowInvisible(component) .then() + .notContains(component, isOptional = true) + .then() + .isAppWindowInvisible(component, isOptional = true) + .then() .isAppWindowVisible(component) } } -fun FlickerTestParameter.appWindowIsVisibleAtEnd( - component: FlickerComponentName -) { - assertWmEnd { - this.isAppWindowVisible(component) - } +fun FlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) { + assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) } } -fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() { - assertLayersEnd { - this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) - } +fun FlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) { + assertWmStart { this.isAppWindowVisible(component) } +} + +fun FlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) { + assertWmEnd { this.isAppWindowVisible(component) } } -fun FlickerTestParameter.dockedStackDividerBecomesVisible() { +fun FlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) { + assertWmStart { this.isAppWindowInvisible(component) } +} + +fun FlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) { + assertWmEnd { this.isAppWindowInvisible(component) } +} + +fun FlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) { + assertWmStart { this.notContains(component) } +} + +fun FlickerTest.appWindowKeepVisible(component: IComponentMatcher) { + assertWm { this.isAppWindowVisible(component) } +} + +fun FlickerTest.dockedStackDividerIsVisibleAtEnd() { + assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) } +} + +fun FlickerTest.dockedStackDividerBecomesVisible() { assertLayers { this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() @@ -135,7 +354,7 @@ fun FlickerTestParameter.dockedStackDividerBecomesVisible() { } } -fun FlickerTestParameter.dockedStackDividerBecomesInvisible() { +fun FlickerTest.dockedStackDividerBecomesInvisible() { assertLayers { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() @@ -143,105 +362,83 @@ fun FlickerTestParameter.dockedStackDividerBecomesInvisible() { } } -fun FlickerTestParameter.dockedStackDividerNotExistsAtEnd() { - assertLayersEnd { - this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) - } +fun FlickerTest.dockedStackDividerNotExistsAtEnd() { + assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) } } -fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd( - rotation: Int, - primaryComponent: FlickerComponentName +fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd( + rotation: PlatformConsts.Rotation, + primaryComponent: IComponentMatcher ) { assertLayersEnd { val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region - visibleRegion(primaryComponent) - .overlaps(getPrimaryRegion(dividerRegion, rotation)) + visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation)) } } -fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisibleAtEnd( - rotation: Int, - primaryComponent: FlickerComponentName +fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( + rotation: PlatformConsts.Rotation, + primaryComponent: IComponentMatcher ) { assertLayersEnd { val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region - visibleRegion(primaryComponent) - .overlaps(getPrimaryRegion(dividerRegion, rotation)) + visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation)) } } -fun FlickerTestParameter.appPairsSecondaryBoundsIsVisibleAtEnd( - rotation: Int, - secondaryComponent: FlickerComponentName +fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd( + rotation: PlatformConsts.Rotation, + secondaryComponent: IComponentMatcher ) { assertLayersEnd { val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region - visibleRegion(secondaryComponent) - .overlaps(getSecondaryRegion(dividerRegion, rotation)) + visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation)) } } -fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd( - rotation: Int, - secondaryComponent: FlickerComponentName +fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd( + rotation: PlatformConsts.Rotation, + secondaryComponent: IComponentMatcher ) { assertLayersEnd { val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region - visibleRegion(secondaryComponent) - .overlaps(getSecondaryRegion(dividerRegion, rotation)) + visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation)) } } -fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region { +fun getPrimaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): Region { val displayBounds = WindowUtils.getDisplayBounds(rotation) - return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - Region.from( - 0, 0, displayBounds.bounds.right, - dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset - ) - } else { + return if (rotation.isRotated()) { Region.from( - 0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset, + 0, + 0, + dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset, displayBounds.bounds.bottom ) - } -} - -fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region { - val displayBounds = WindowUtils.getDisplayBounds(rotation) - return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - Region.from( - 0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset, - displayBounds.bounds.right, displayBounds.bounds.bottom - ) } else { Region.from( - dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0, - displayBounds.bounds.right, displayBounds.bounds.bottom + 0, + 0, + displayBounds.bounds.right, + dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset ) } } -fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region { - val displayBounds = WindowUtils.getDisplayBounds(rotation) - return if (displayBounds.width > displayBounds.height) { - Region.from(0, 0, dividerRegion.bounds.left, displayBounds.bounds.bottom) - } else { - Region.from(0, 0, displayBounds.bounds.right, dividerRegion.bounds.top) - } -} - -fun getSplitRightBottomRegion(dividerRegion: Region, rotation: Int): Region { +fun getSecondaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): Region { val displayBounds = WindowUtils.getDisplayBounds(rotation) - return if (displayBounds.width > displayBounds.height) { + return if (rotation.isRotated()) { Region.from( - dividerRegion.bounds.right, 0, displayBounds.bounds.right, + dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, + 0, + displayBounds.bounds.right, displayBounds.bounds.bottom ) } else { Region.from( - 0, dividerRegion.bounds.bottom, displayBounds.bounds.right, + 0, + dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset, + displayBounds.bounds.right, displayBounds.bounds.bottom ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index f56eb6e783aa..651d9356d9ba 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -15,11 +15,21 @@ */ @file:JvmName("CommonConstants") + package com.android.wm.shell.flicker -import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.ComponentNameMatcher const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" -val APP_PAIR_SPLIT_DIVIDER_COMPONENT = FlickerComponentName("", "AppPairSplitDivider#") -val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#") -val SPLIT_SCREEN_DIVIDER_COMPONENT = FlickerComponentName("", "StageCoordinatorSplitDivider#") +const val LAUNCHER_UI_PACKAGE_NAME = "com.google.android.apps.nexuslauncher" +val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#") +val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#") +val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#") +val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") + +enum class Direction { + UP, + DOWN, + LEFT, + RIGHT +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt new file mode 100644 index 000000000000..87b94ff8668b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 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.app.Instrumentation +import android.content.Context +import android.provider.Settings +import android.util.Log +import com.android.compatibility.common.util.SystemUtil +import java.io.IOException + +object MultiWindowUtils { + private fun executeShellCommand(instrumentation: Instrumentation, cmd: String) { + try { + SystemUtil.runShellCommand(instrumentation, cmd) + } catch (e: IOException) { + Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e") + } + } + + fun getDevEnableNonResizableMultiWindow(context: Context): Int = + Settings.Global.getInt( + context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW + ) + + fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) = + Settings.Global.putInt( + context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + configValue + ) + + fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) = + executeShellCommand( + instrumentation, + createConfigSupportsNonResizableMultiWindowCommand(configValue) + ) + + fun resetMultiWindowConfig(instrumentation: Instrumentation) = + executeShellCommand(instrumentation, resetMultiWindowConfigCommand) + + private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String = + "wm set-multi-window-config --supportsNonResizable $configValue" + + private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config" +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt index 51f7a18f60dd..e0ef92457f58 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt @@ -51,7 +51,7 @@ class NotificationListener : NotificationListenerService() { private const val CMD_NOTIFICATION_ALLOW_LISTENER = "cmd notification allow_listener %s" private const val CMD_NOTIFICATION_DISALLOW_LISTENER = - "cmd notification disallow_listener %s" + "cmd notification disallow_listener %s" private const val COMPONENT_NAME = "com.android.wm.shell.flicker/.NotificationListener" private var instance: NotificationListener? = null @@ -79,25 +79,23 @@ class NotificationListener : NotificationListenerService() { ): StatusBarNotification? { instance?.run { return notifications.values.firstOrNull(predicate) - } ?: throw IllegalStateException("NotificationListenerService is not connected") + } + ?: throw IllegalStateException("NotificationListenerService is not connected") } fun waitForNotificationToAppear( predicate: (StatusBarNotification) -> Boolean ): StatusBarNotification? { instance?.let { - return waitForResult(extractor = { - it.notifications.values.firstOrNull(predicate) - }).second - } ?: throw IllegalStateException("NotificationListenerService is not connected") + return waitForResult(extractor = { it.notifications.values.firstOrNull(predicate) }) + .second + } + ?: throw IllegalStateException("NotificationListenerService is not connected") } - fun waitForNotificationToDisappear( - predicate: (StatusBarNotification) -> Boolean - ): Boolean { - return instance?.let { - wait { it.notifications.values.none(predicate) } - } ?: throw IllegalStateException("NotificationListenerService is not connected") + fun waitForNotificationToDisappear(predicate: (StatusBarNotification) -> Boolean): Boolean { + return instance?.let { wait { it.notifications.values.none(predicate) } } + ?: throw IllegalStateException("NotificationListenerService is not connected") } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt index 4d87ec9e872f..556cb06f3ca1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt @@ -15,6 +15,7 @@ */ @file:JvmName("WaitUtils") + package com.android.wm.shell.flicker import android.os.SystemClock diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt index 278ba9b0f4db..996b677470fe 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt @@ -17,40 +17,38 @@ package com.android.wm.shell.flicker.bubble import android.app.INotificationManager -import android.app.Instrumentation import android.app.NotificationManager import android.content.Context +import android.content.pm.PackageManager import android.os.ServiceManager -import android.view.Surface -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.Flicker -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.IFlickerTestData +import com.android.server.wm.flicker.helpers.LaunchBubbleHelper import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE -import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.BaseTest import org.junit.runners.Parameterized -/** - * Base configurations for Bubble flicker tests - */ -abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) { +/** Base configurations for Bubble flicker tests */ +abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) { - protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() protected val context: Context = instrumentation.context protected val testApp = LaunchBubbleHelper(instrumentation) - protected val notifyManager = INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)) - - protected val uid = context.packageManager.getApplicationInfo( - testApp.component.packageName, 0).uid + private val notifyManager = + INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE) + ) - protected abstract val transition: FlickerBuilder.() -> Unit + private val uid = + context.packageManager + .getApplicationInfo(testApp.`package`, PackageManager.ApplicationInfoFlags.of(0)) + .uid @JvmOverloads protected open fun buildTransition( @@ -58,46 +56,41 @@ abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) { ): FlickerBuilder.() -> Unit { return { setup { - test { - notifyManager.setBubblesAllowed(testApp.component.packageName, - uid, NotificationManager.BUBBLE_PREFERENCE_ALL) - testApp.launchViaIntent(wmHelper) - waitAndGetAddBubbleBtn() - waitAndGetCancelAllBtn() - } + notifyManager.setBubblesAllowed( + testApp.`package`, + uid, + NotificationManager.BUBBLE_PREFERENCE_ALL + ) + testApp.launchViaIntent(wmHelper) + waitAndGetAddBubbleBtn() + waitAndGetCancelAllBtn() } teardown { - test { - notifyManager.setBubblesAllowed(testApp.component.packageName, - uid, NotificationManager.BUBBLE_PREFERENCE_NONE) - testApp.exit() - } + notifyManager.setBubblesAllowed( + testApp.`package`, + uid, + NotificationManager.BUBBLE_PREFERENCE_NONE + ) + testApp.exit() } extraSpec(this) } } - protected fun Flicker.waitAndGetAddBubbleBtn(): UiObject2? = device.wait(Until.findObject( - By.text("Add Bubble")), FIND_OBJECT_TIMEOUT) - protected fun Flicker.waitAndGetCancelAllBtn(): UiObject2? = device.wait(Until.findObject( - By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - transition(this) - } - } + protected fun IFlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? = + device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT) + protected fun IFlickerTestData.waitAndGetCancelAllBtn(): UiObject2? = + device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT) companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 3) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } const val FIND_OBJECT_TIMEOUT = 2000L diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt index b137e92881a5..7a86c2557bb4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt @@ -24,12 +24,11 @@ import android.view.WindowManager import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import org.junit.runner.RunWith +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Test +import org.junit.runner.RunWith import org.junit.runners.Parameterized /** @@ -38,30 +37,33 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:DismissBubbleScreen` * * Actions: + * ``` * Dismiss a bubble notification + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group4 -open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class DismissBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) { private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager private val displaySize = DisplayMetrics() + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = buildTransition { setup { - eachRun { - val addBubbleBtn = waitAndGetAddBubbleBtn() - addBubbleBtn?.click() ?: error("Add Bubble not found") - } + val addBubbleBtn = waitAndGetAddBubbleBtn() + addBubbleBtn?.click() ?: error("Add Bubble not found") } transitions { - wm.run { wm.getDefaultDisplay().getMetrics(displaySize) } + wm.run { wm.defaultDisplay.getMetrics(displaySize) } val dist = Point((displaySize.widthPixels / 2), displaySize.heightPixels) - val showBubble = device.wait(Until.findObject( - By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT) + val showBubble = + device.wait( + Until.findObject(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), + FIND_OBJECT_TIMEOUT + ) showBubble?.run { drag(dist, 1000) } ?: error("Show bubble not found") } } @@ -69,8 +71,6 @@ open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScree @Presubmit @Test open fun testAppIsAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(testApp.component) - } + flicker.assertLayers { this.isVisible(testApp) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt index f288b0a24d9d..0cda626f8286 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt @@ -20,12 +20,11 @@ import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import org.junit.runner.RunWith +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Test +import org.junit.runner.RunWith import org.junit.runners.Parameterized /** @@ -34,27 +33,30 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen` * * Actions: + * ``` * Launch an app and enable app's bubble notification * Send a bubble notification * The activity for the bubble is launched + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group4 -open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class ExpandBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) { + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = buildTransition { setup { - test { - val addBubbleBtn = waitAndGetAddBubbleBtn() - addBubbleBtn?.click() ?: error("Add Bubble not found") - } + val addBubbleBtn = waitAndGetAddBubbleBtn() + addBubbleBtn?.click() ?: error("Add Bubble not found") } transitions { - val showBubble = device.wait(Until.findObject( - By.res("com.android.systemui", "bubble_view")), FIND_OBJECT_TIMEOUT) + val showBubble = + device.wait( + Until.findObject(By.res("com.android.systemui", "bubble_view")), + FIND_OBJECT_TIMEOUT + ) showBubble?.run { showBubble.click() } ?: error("Bubble notify not found") } } @@ -62,8 +64,6 @@ open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen @Presubmit @Test open fun testAppIsAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(testApp.component) - } + flicker.assertLayers { this.isVisible(testApp) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt index 684e5cad0e67..379d5e90406b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt @@ -16,21 +16,21 @@ package com.android.wm.shell.flicker.bubble -import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.Postsubmit import android.view.WindowInsets import android.view.WindowManager -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd +import com.android.server.wm.flicker.navBarLayerPositionAtEnd import org.junit.Assume -import org.junit.runner.RunWith import org.junit.Test +import org.junit.runner.RunWith import org.junit.runners.Parameterized /** @@ -39,44 +39,48 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:LaunchBubbleFromLockScreen` * * Actions: + * ``` * Launch an bubble from notification on lock screen + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group4 -class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +class LaunchBubbleFromLockScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) { + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = buildTransition { setup { - eachRun { - val addBubbleBtn = waitAndGetAddBubbleBtn() - addBubbleBtn?.click() ?: error("Bubble widget not found") - device.sleep() - wmHelper.waitFor("noAppWindowsOnTop") { - it.wmState.topVisibleAppWindow.isEmpty() - } - device.wakeUp() - } + val addBubbleBtn = waitAndGetAddBubbleBtn() + addBubbleBtn?.click() ?: error("Bubble widget not found") + device.sleep() + wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() + device.wakeUp() } transitions { // Swipe & wait for the notification shade to expand so all can be seen - val wm = context.getSystemService(WindowManager::class.java) - val metricInsets = wm.getCurrentWindowMetrics().windowInsets - val insets = metricInsets.getInsetsIgnoringVisibility( - WindowInsets.Type.statusBars() - or WindowInsets.Type.displayCutout()) - device.swipe(100, insets.top + 100, 100, device.getDisplayHeight() / 2, 4) + val wm = + context.getSystemService(WindowManager::class.java) + ?: error("Unable to obtain WM service") + val metricInsets = wm.currentWindowMetrics.windowInsets + val insets = + metricInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout() + ) + device.swipe(100, insets.top + 100, 100, device.displayHeight / 2, 4) device.waitForIdle(2000) instrumentation.uiAutomation.syncInputTransactions() - val notification = device.wait(Until.findObject( - By.text("BubbleChat")), FIND_OBJECT_TIMEOUT) + val notification = + device.wait(Until.findObject(By.text("BubbleChat")), FIND_OBJECT_TIMEOUT) notification?.click() ?: error("Notification not found") instrumentation.uiAutomation.syncInputTransactions() - val showBubble = device.wait(Until.findObject( - By.res("com.android.systemui", "bubble_view")), FIND_OBJECT_TIMEOUT) + val showBubble = + device.wait( + Until.findObject(By.res("com.android.systemui", "bubble_view")), + FIND_OBJECT_TIMEOUT + ) showBubble?.click() ?: error("Bubble notify not found") instrumentation.uiAutomation.syncInputTransactions() val cancelAllBtn = waitAndGetCancelAllBtn() @@ -84,21 +88,53 @@ class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScr } } - @Presubmit + @FlakyTest(bugId = 242088970) @Test fun testAppIsVisibleAtEnd() { - Assume.assumeFalse(isShellTransitionsEnabled) - testSpec.assertLayersEnd { - this.isVisible(testApp.component) - } + flicker.assertLayersEnd { this.isVisible(testApp) } + } + + @Postsubmit + @Test + fun navBarLayerIsVisibleAtEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarLayerIsVisibleAtEnd() + } + + @Postsubmit + @Test + fun navBarLayerPositionAtEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarLayerPositionAtEnd() } + /** {@inheritDoc} */ @FlakyTest @Test - fun testAppIsVisibleAtEnd_ShellTransit() { - Assume.assumeTrue(isShellTransitionsEnabled) - testSpec.assertLayersEnd { - this.isVisible(testApp.component) - } + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() { + Assume.assumeTrue(flicker.scenario.isGesturalNavigation) + super.navBarLayerIsVisibleAtStartAndEnd() + } + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() { + Assume.assumeTrue(flicker.scenario.isGesturalNavigation) + super.navBarLayerPositionAtStartAndEnd() + } + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() { + Assume.assumeTrue(flicker.scenario.isGesturalNavigation) + super.navBarWindowIsAlwaysVisible() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt index 0bb4d398bff4..5c0f8540db02 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt @@ -17,11 +17,12 @@ package com.android.wm.shell.flicker.bubble import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import androidx.test.filters.RequiresDevice +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -32,28 +33,34 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen` * * Actions: + * ``` * Launch an app and enable app's bubble notification * Send a bubble notification + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group4 -open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class LaunchBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) { + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = buildTransition { transitions { val addBubbleBtn = waitAndGetAddBubbleBtn() addBubbleBtn?.click() ?: error("Bubble widget not found") + + device.wait( + Until.findObjects(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), + FIND_OBJECT_TIMEOUT + ) + ?: error("No bubbles found") } } @Presubmit @Test open fun testAppIsAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(testApp.component) - } + flicker.assertLayers { this.isVisible(testApp) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt index 8d1e315e2d5e..b3a2ad309b27 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt @@ -20,16 +20,16 @@ import android.os.SystemClock import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.Before -import org.junit.runner.RunWith import org.junit.Test +import org.junit.runner.RunWith import org.junit.runners.Parameterized /** @@ -38,38 +38,47 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:MultiBubblesScreen` * * Actions: + * ``` * Switch in different bubble notifications + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group4 -open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class MultiBubblesScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) { @Before open fun before() { Assume.assumeFalse(isShellTransitionsEnabled) } + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = buildTransition { setup { - test { - for (i in 1..3) { - val addBubbleBtn = waitAndGetAddBubbleBtn() - addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found") - } - val showBubble = device.wait(Until.findObject( - By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT) - showBubble?.run { showBubble.click() } ?: error("Show bubble not found") + for (i in 1..3) { + val addBubbleBtn = waitAndGetAddBubbleBtn() ?: error("Add Bubble not found") + addBubbleBtn.click() SystemClock.sleep(1000) } + val showBubble = + device.wait( + Until.findObject(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), + FIND_OBJECT_TIMEOUT + ) + ?: error("Show bubble not found") + showBubble.click() + SystemClock.sleep(1000) } transitions { - val bubbles = device.wait(Until.findObjects( - By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT) + val bubbles: List<UiObject2> = + device.wait( + Until.findObjects(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), + FIND_OBJECT_TIMEOUT + ) + ?: error("No bubbles found") for (entry in bubbles) { - entry?.run { entry.click() } ?: error("Bubble not found") + entry.click() SystemClock.sleep(1000) } } @@ -78,8 +87,6 @@ open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen @Presubmit @Test open fun testAppIsAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(testApp.component) - } + flicker.assertLayers { this.isVisible(testApp) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt index ddebb6fed636..191f4fa893f9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt @@ -16,12 +16,11 @@ package com.android.wm.shell.flicker.bubble -import androidx.test.filters.FlakyTest +import android.platform.test.annotations.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.FlickerTest import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.Before import org.junit.runner.RunWith @@ -30,13 +29,10 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group4 @FlakyTest(bugId = 217777115) -class MultiBubblesScreenShellTransit( - testSpec: FlickerTestParameter -) : MultiBubblesScreen(testSpec) { +class MultiBubblesScreenShellTransit(flicker: FlickerTest) : MultiBubblesScreen(flicker) { @Before override fun before() { Assume.assumeTrue(isShellTransitionsEnabled) } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt deleted file mode 100644 index 41cd31aabf05..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2020 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.helpers - -import android.app.Instrumentation -import com.android.server.wm.flicker.Flicker -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.common.region.Region - -class AppPairsHelper( - instrumentation: Instrumentation, - activityLabel: String, - component: FlickerComponentName -) : BaseAppHelper(instrumentation, activityLabel, component) { - fun getPrimaryBounds(dividerBounds: Region): Region { - val primaryAppBounds = Region.from(0, 0, dividerBounds.bounds.right, - dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset) - return primaryAppBounds - } - - fun getSecondaryBounds(dividerBounds: Region): Region { - val displayBounds = WindowUtils.displayBounds - val secondaryAppBounds = Region.from(0, - dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset, - displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarFrameHeight) - return secondaryAppBounds - } - - companion object { - const val TEST_REPETITIONS = 1 - const val TIMEOUT_MS = 3_000L - - fun Flicker.waitAppsShown(app1: SplitScreenHelper?, app2: SplitScreenHelper?) { - wmHelper.waitFor("primaryAndSecondaryAppsVisible") { dump -> - val primaryAppVisible = app1?.let { - dump.wmState.isWindowSurfaceShown(app1.defaultWindowName) - } ?: false - val secondaryAppVisible = app2?.let { - dump.wmState.isWindowSurfaceShown(app2.defaultWindowName) - } ?: false - primaryAppVisible && secondaryAppVisible - } - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt deleted file mode 100644 index 3dd9e0572947..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2020 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.helpers - -import android.app.Instrumentation -import android.content.pm.PackageManager.FEATURE_LEANBACK -import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY -import android.os.SystemProperties -import android.support.test.launcherhelper.LauncherStrategyFactory -import android.util.Log -import androidx.test.uiautomator.By -import androidx.test.uiautomator.UiObject2 -import androidx.test.uiautomator.Until -import com.android.compatibility.common.util.SystemUtil -import com.android.server.wm.flicker.helpers.StandardAppHelper -import com.android.server.wm.traces.common.FlickerComponentName -import java.io.IOException - -abstract class BaseAppHelper( - instrumentation: Instrumentation, - launcherName: String, - component: FlickerComponentName -) : StandardAppHelper( - instrumentation, - launcherName, - component, - LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy -) { - private val appSelector = By.pkg(component.packageName).depth(0) - - protected val isTelevision: Boolean - get() = context.packageManager.run { - hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY) - } - - val defaultWindowName: String - get() = component.toWindowName() - - val ui: UiObject2? - get() = uiDevice.findObject(appSelector) - - fun waitUntilClosed(): Boolean { - return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS) - } - - companion object { - private const val APP_CLOSE_WAIT_TIME_MS = 3_000L - - fun isShellTransitionsEnabled() = - SystemProperties.getBoolean("persist.wm.debug.shell_transit", false) - - fun executeShellCommand(instrumentation: Instrumentation, cmd: String) { - try { - SystemUtil.runShellCommand(instrumentation, cmd) - } catch (e: IOException) { - Log.e("BaseAppHelper", "executeShellCommand error! $e") - } - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt deleted file mode 100644 index 471e010cf560..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2020 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.helpers - -import android.app.Instrumentation -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.wm.shell.flicker.testapp.Components - -class FixedAppHelper(instrumentation: Instrumentation) : BaseAppHelper( - instrumentation, - Components.FixedActivity.LABEL, - Components.FixedActivity.COMPONENT.toFlickerComponent() -)
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt deleted file mode 100644 index cc5b9f9eb26d..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2020 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.helpers - -import android.app.Instrumentation -import androidx.test.uiautomator.By -import androidx.test.uiautomator.UiDevice -import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.helpers.FIND_TIMEOUT -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.testapp.Components - -open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper( - instrumentation, - Components.ImeActivity.LABEL, - Components.ImeActivity.COMPONENT.toFlickerComponent() -) { - /** - * Opens the IME and wait for it to be displayed - * - * @param wmHelper Helper used to wait for WindowManager states - */ - @JvmOverloads - open fun openIME(wmHelper: WindowManagerStateHelper? = null) { - if (!isTelevision) { - val editText = uiDevice.wait( - Until.findObject(By.res(getPackage(), "plain_text_input")), - FIND_TIMEOUT) - - require(editText != null) { - "Text field not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)" - } - editText.click() - waitAndAssertIMEShown(uiDevice, wmHelper) - } else { - // If we do the same thing as above - editText.click() - on TV, that's going to force TV - // into the touch mode. We really don't want that. - launchViaIntent(action = Components.ImeActivity.ACTION_OPEN_IME) - } - } - - protected fun waitAndAssertIMEShown( - device: UiDevice, - wmHelper: WindowManagerStateHelper? = null - ) { - if (wmHelper == null) { - device.waitForIdle() - } else { - wmHelper.waitImeShown() - } - } - - /** - * Opens the IME and wait for it to be gone - * - * @param wmHelper Helper used to wait for WindowManager states - */ - @JvmOverloads - open fun closeIME(wmHelper: WindowManagerStateHelper? = null) { - if (!isTelevision) { - uiDevice.pressBack() - // Using only the AccessibilityInfo it is not possible to identify if the IME is active - if (wmHelper == null) { - uiDevice.waitForIdle() - } else { - wmHelper.waitImeGone() - } - } else { - // While pressing the back button should close the IME on TV as well, it may also lead - // to the app closing. So let's instead just ask the app to close the IME. - launchViaIntent(action = Components.ImeActivity.ACTION_CLOSE_IME) - } - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt deleted file mode 100644 index 6695c17ed514..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2021 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.helpers - -import android.app.Instrumentation -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.wm.shell.flicker.testapp.Components - -class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper( - instrumentation, - Components.LaunchBubbleActivity.LABEL, - Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent() -) { - - companion object { - const val TEST_REPETITIONS = 1 - const val TIMEOUT_MS = 3_000L - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt deleted file mode 100644 index 12ccbafce651..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2020 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.helpers - -import android.app.Instrumentation -import android.content.Context -import android.provider.Settings -import com.android.server.wm.traces.common.FlickerComponentName - -class MultiWindowHelper( - instrumentation: Instrumentation, - activityLabel: String, - componentsInfo: FlickerComponentName -) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) { - - companion object { - fun getDevEnableNonResizableMultiWindow(context: Context): Int = - Settings.Global.getInt(context.contentResolver, - Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) - - fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) = - Settings.Global.putInt(context.contentResolver, - Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, configValue) - - fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) = - executeShellCommand( - instrumentation, - createConfigSupportsNonResizableMultiWindowCommand(configValue)) - - fun resetMultiWindowConfig(instrumentation: Instrumentation) = - executeShellCommand(instrumentation, resetMultiWindowConfigCommand) - - private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String = - "wm set-multi-window-config --supportsNonResizable $configValue" - - private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config" - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt deleted file mode 100644 index 8157a4e453af..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2020 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.helpers - -import android.app.Instrumentation -import android.media.session.MediaController -import android.media.session.MediaSessionManager -import android.os.SystemClock -import androidx.test.uiautomator.By -import androidx.test.uiautomator.BySelector -import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.helpers.FIND_TIMEOUT -import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE -import com.android.server.wm.traces.common.Rect -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow -import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild -import com.android.wm.shell.flicker.testapp.Components - -class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( - instrumentation, - Components.PipActivity.LABEL, - Components.PipActivity.COMPONENT.toFlickerComponent() -) { - private val mediaSessionManager: MediaSessionManager - get() = context.getSystemService(MediaSessionManager::class.java) - ?: error("Could not get MediaSessionManager") - - private val mediaController: MediaController? - get() = mediaSessionManager.getActiveSessions(null).firstOrNull { - it.packageName == component.packageName - } - - fun clickObject(resId: String) { - val selector = By.res(component.packageName, resId) - val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object") - - if (!isTelevision) { - obj.click() - } else { - focusOnObject(selector) || error("Could not focus on `$resId` object") - uiDevice.pressDPadCenter() - } - } - - /** - * Launches the app through an intent instead of interacting with the launcher and waits - * until the app window is in PIP mode - */ - @JvmOverloads - fun launchViaIntentAndWaitForPip( - wmHelper: WindowManagerStateHelper, - expectedWindowName: String = "", - action: String? = null, - stringExtras: Map<String, String> - ) { - launchViaIntentAndWaitShown( - wmHelper, expectedWindowName, action, stringExtras, - waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition) - ) - } - - /** - * Expand the PIP window back to full screen via intent and wait until the app is visible - */ - fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) = - launchViaIntentAndWaitShown(wmHelper) - - private fun focusOnObject(selector: BySelector): Boolean { - // We expect all the focusable UI elements to be arranged in a way so that it is possible - // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top" - // from "the bottom". - repeat(FOCUS_ATTEMPTS) { - uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true } - ?: error("The object we try to focus on is gone.") - - uiDevice.pressDPadDown() - uiDevice.waitForIdle() - } - return false - } - - @JvmOverloads - fun clickEnterPipButton(wmHelper: WindowManagerStateHelper? = null) { - clickObject(ENTER_PIP_BUTTON_ID) - - // Wait on WMHelper or simply wait for 3 seconds - wmHelper?.waitPipShown() ?: SystemClock.sleep(3_000) - // when entering pip, the dismiss button is visible at the start. to ensure the pip - // animation is complete, wait until the pip dismiss button is no longer visible. - // b/176822698: dismiss-only state will be removed in the future - uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT) - } - - fun enableEnterPipOnUserLeaveHint() { - clickObject(ENTER_PIP_ON_USER_LEAVE_HINT) - } - - fun enableAutoEnterForPipActivity() { - clickObject(ENTER_PIP_AUTOENTER) - } - - fun clickStartMediaSessionButton() { - clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID) - } - - fun checkWithCustomActionsCheckbox() = uiDevice - .findObject(By.res(component.packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID)) - ?.takeIf { it.isCheckable } - ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) } - ?: error("'With custom actions' checkbox not found") - - fun pauseMedia() = mediaController?.transportControls?.pause() - ?: error("No active media session found") - - fun stopMedia() = mediaController?.transportControls?.stop() - ?: error("No active media session found") - - @Deprecated( - "Use PipAppHelper.closePipWindow(wmHelper) instead", - ReplaceWith("closePipWindow(wmHelper)") - ) - fun closePipWindow() { - if (isTelevision) { - uiDevice.closeTvPipWindow() - } else { - closePipWindow(WindowManagerStateHelper(mInstrumentation)) - } - } - - private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect { - val windowRegion = wmHelper.getWindowRegion(component) - require(!windowRegion.isEmpty) { - "Unable to find a PIP window in the current state" - } - return windowRegion.bounds - } - - /** - * Taps the pip window and dismisses it by clicking on the X button. - */ - fun closePipWindow(wmHelper: WindowManagerStateHelper) { - if (isTelevision) { - uiDevice.closeTvPipWindow() - } else { - val windowRect = getWindowRect(wmHelper) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - // search and interact with the dismiss button - val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") - uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) - val dismissPipObject = uiDevice.findObject(dismissSelector) - ?: error("PIP window dismiss button not found") - val dismissButtonBounds = dismissPipObject.visibleBounds - uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY()) - } - - // Wait for animation to complete. - wmHelper.waitPipGone() - wmHelper.waitForHomeActivityVisible() - } - - /** - * Close the pip window by pressing the expand button - */ - fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) { - val windowRect = getWindowRect(wmHelper) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - // search and interact with the expand button - val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button") - uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT) - val expandPipObject = uiDevice.findObject(expandSelector) - ?: error("PIP window expand button not found") - val expandButtonBounds = expandPipObject.visibleBounds - uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY()) - wmHelper.waitPipGone() - wmHelper.waitForAppTransitionIdle() - } - - /** - * Double click on the PIP window to expand it - */ - fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) { - val windowRect = getWindowRect(wmHelper) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - wmHelper.waitForAppTransitionIdle() - } - - companion object { - private const val FOCUS_ATTEMPTS = 20 - private const val ENTER_PIP_BUTTON_ID = "enter_pip" - private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions" - private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start" - private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual" - private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter" - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt deleted file mode 100644 index 4d0fbc4a0e38..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2021 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.helpers - -import android.app.Instrumentation -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.wm.shell.flicker.testapp.Components - -class SimpleAppHelper(instrumentation: Instrumentation) : BaseAppHelper( - instrumentation, - Components.SimpleActivity.LABEL, - Components.SimpleActivity.COMPONENT.toFlickerComponent() -)
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt deleted file mode 100644 index 49eca63a23ec..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2020 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.helpers - -import android.app.Instrumentation -import android.graphics.Point -import android.os.SystemClock -import android.view.InputDevice -import android.view.MotionEvent -import androidx.test.uiautomator.By -import androidx.test.uiautomator.BySelector -import androidx.test.uiautomator.UiDevice -import androidx.test.uiautomator.Until -import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME -import com.android.wm.shell.flicker.testapp.Components -import org.junit.Assert - -class SplitScreenHelper( - instrumentation: Instrumentation, - activityLabel: String, - componentsInfo: FlickerComponentName -) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) { - - companion object { - const val TEST_REPETITIONS = 1 - const val TIMEOUT_MS = 3_000L - const val DRAG_DURATION_MS = 1_000L - const val NOTIFICATION_SCROLLER = "notification_stack_scroller" - const val GESTURE_STEP_MS = 16L - - private val notificationScrollerSelector: BySelector - get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER) - private val notificationContentSelector: BySelector - get() = By.text("Notification content") - - fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper( - instrumentation, - Components.SplitScreenActivity.LABEL, - Components.SplitScreenActivity.COMPONENT.toFlickerComponent() - ) - - fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper( - instrumentation, - Components.SplitScreenSecondaryActivity.LABEL, - Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent() - ) - - fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper( - instrumentation, - Components.NonResizeableActivity.LABEL, - Components.NonResizeableActivity.COMPONENT.toFlickerComponent() - ) - - fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper( - instrumentation, - Components.SendNotificationActivity.LABEL, - Components.SendNotificationActivity.COMPONENT.toFlickerComponent() - ) - - fun dragFromNotificationToSplit( - instrumentation: Instrumentation, - device: UiDevice, - wmHelper: WindowManagerStateHelper - ) { - val displayBounds = wmHelper.currentState.layerState - .displays.firstOrNull { !it.isVirtual } - ?.layerStackSpace - ?: error("Display not found") - - // Pull down the notifications - device.swipe( - displayBounds.centerX(), 5, - displayBounds.centerX(), displayBounds.bottom, 20 /* steps */ - ) - SystemClock.sleep(TIMEOUT_MS) - - // Find the target notification - val notificationScroller = device.wait( - Until.findObject(notificationScrollerSelector), TIMEOUT_MS - ) - var notificationContent = notificationScroller.findObject(notificationContentSelector) - - while (notificationContent == null) { - device.swipe( - displayBounds.centerX(), displayBounds.centerY(), - displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */ - ) - notificationContent = notificationScroller.findObject(notificationContentSelector) - } - - // Drag to split - var dragStart = notificationContent.visibleCenter - var dragMiddle = Point(dragStart.x + 50, dragStart.y) - var dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4) - val downTime = SystemClock.uptimeMillis() - - touch( - instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, - TIMEOUT_MS, dragStart - ) - // It needs a horizontal movement to trigger the drag - touchMove( - instrumentation, downTime, SystemClock.uptimeMillis(), - DRAG_DURATION_MS, dragStart, dragMiddle - ) - touchMove( - instrumentation, downTime, SystemClock.uptimeMillis(), - DRAG_DURATION_MS, dragMiddle, dragEnd - ) - // Wait for a while to start splitting - SystemClock.sleep(TIMEOUT_MS) - touch( - instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(), - GESTURE_STEP_MS, dragEnd - ) - SystemClock.sleep(TIMEOUT_MS) - } - - fun touch( - instrumentation: Instrumentation, - action: Int, - downTime: Long, - eventTime: Long, - duration: Long, - point: Point - ) { - val motionEvent = MotionEvent.obtain( - downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0 - ) - motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN - instrumentation.uiAutomation.injectInputEvent(motionEvent, true) - motionEvent.recycle() - SystemClock.sleep(duration) - } - - fun touchMove( - instrumentation: Instrumentation, - downTime: Long, - eventTime: Long, - duration: Long, - from: Point, - to: Point - ) { - val steps: Long = duration / GESTURE_STEP_MS - var currentTime = eventTime - var currentX = from.x.toFloat() - var currentY = from.y.toFloat() - val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat() - val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat() - - for (i in 1..steps) { - val motionMove = MotionEvent.obtain( - downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0 - ) - motionMove.source = InputDevice.SOURCE_TOUCHSCREEN - instrumentation.uiAutomation.injectInputEvent(motionMove, true) - motionMove.recycle() - - currentTime += GESTURE_STEP_MS - if (i == steps - 1) { - currentX = to.x.toFloat() - currentY = to.y.toFloat() - } else { - currentX += stepX - currentY += stepY - } - SystemClock.sleep(GESTURE_STEP_MS) - } - } - - fun createShortcutOnHotseatIfNotExist( - taplInstrumentation: LauncherInstrumentation, - appName: String - ) { - taplInstrumentation.workspace - .deleteAppIcon(taplInstrumentation.workspace.getHotseatAppIcon(0)) - val allApps = taplInstrumentation.workspace.switchToAllApps() - allApps.freeze() - try { - val appIconSrc = allApps.getAppIcon(appName) - Assert.assertNotNull("Unable to find app icon", appIconSrc) - val appIconDest = appIconSrc.dragToHotseat(0) - Assert.assertNotNull("Unable to drag app icon on hotseat", appIconDest) - } finally { - allApps.unfreeze() - } - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index ce624f2b5bbe..4f3facb5b484 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -17,12 +17,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice -import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -36,69 +35,60 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest` * * Actions: + * ``` * Launch an app in full screen * Select "Auto-enter PiP" radio button * Press Home button or swipe up to go Home and put [pipApp] in pip mode - * + * ``` * Notes: + * ``` * 1. All assertions are inherited from [EnterPipTest] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @FlakyTest(bugId = 238367575) -@Group3 -class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) { - protected val taplInstrumentation = LauncherInstrumentation() - /** - * Defines the transition used to run the test - */ +class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipTest(flicker) { + /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit get() = { - setupAndTeardown(this) setup { - eachRun { - pipApp.launchViaIntent(wmHelper) - pipApp.enableAutoEnterForPipActivity() - } + pipApp.launchViaIntent(wmHelper) + pipApp.enableAutoEnterForPipActivity() } teardown { - eachRun { - // close gracefully so that onActivityUnpinned() can be called before force exit - pipApp.closePipWindow(wmHelper) - pipApp.exit(wmHelper) - } - } - transitions { - taplInstrumentation.goHome() + // close gracefully so that onActivityUnpinned() can be called before force exit + pipApp.closePipWindow(wmHelper) + pipApp.exit(wmHelper) } + transitions { tapl.goHome() } } + @FlakyTest(bugId = 256863309) + @Test override fun pipLayerReduces() { - val layerName = pipApp.component.toLayerName() - testSpec.assertLayers { - val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } + flicker.assertLayers { + val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } pipLayerList.zipWithNext { previous, current -> current.visibleRegion.notBiggerThan(previous.visibleRegion.region) } } } - /** - * Checks that [pipApp] window is animated towards default position in right bottom corner - */ + /** Checks that [pipApp] window is animated towards default position in right bottom corner */ + @Presubmit @Test fun pipLayerMovesTowardsRightBottomCorner() { // in gestural nav the swipe makes PiP first go upwards - Assume.assumeFalse(testSpec.isGesturalNavigation) - val layerName = pipApp.component.toLayerName() - testSpec.assertLayers { - val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) + flicker.assertLayers { + val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } // Pip animates towards the right bottom corner, but because it is being resized at the // same time, it is possible it shrinks first quickly below the default position and get // moved up after that in just few last frames @@ -108,9 +98,11 @@ class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest( } } + @Presubmit + @Test override fun focusChanges() { // in gestural nav the focus goes to different activity on swipe up - Assume.assumeFalse(testSpec.isGesturalNavigation) + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) super.focusChanges() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt deleted file mode 100644 index f9b08000290f..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2021 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. - */ - -@file:JvmName("CommonAssertions") -package com.android.wm.shell.flicker.pip - -internal const val PIP_WINDOW_COMPONENT = "PipMenuActivity" diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index 953f59a1f70b..8a694f770ab0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -16,14 +16,14 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice -import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.FixMethodOrder +import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -34,78 +34,82 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:EnterPipOnUserLeaveHintTest` * * Actions: + * ``` * Launch an app in full screen * Select "Via code behind" radio button * Press Home button or swipe up to go Home and put [pipApp] in pip mode - * + * ``` * Notes: + * ``` * 1. All assertions are inherited from [EnterPipTest] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) { - protected val taplInstrumentation = LauncherInstrumentation() - /** - * Defines the transition used to run the test - */ +open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTest(flicker) { + /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit get() = { - setupAndTeardown(this) setup { - eachRun { - pipApp.launchViaIntent(wmHelper) - pipApp.enableEnterPipOnUserLeaveHint() - } + pipApp.launchViaIntent(wmHelper) + pipApp.enableEnterPipOnUserLeaveHint() } teardown { - eachRun { - pipApp.exit(wmHelper) - } - } - transitions { - taplInstrumentation.goHome() + // close gracefully so that onActivityUnpinned() can be called before force exit + pipApp.closePipWindow(wmHelper) + pipApp.exit(wmHelper) } + transitions { tapl.goHome() } } + @Presubmit + @Test override fun pipAppLayerAlwaysVisible() { - if (!testSpec.isGesturalNavigation) super.pipAppLayerAlwaysVisible() else { + if (!flicker.scenario.isGesturalNavigation) super.pipAppLayerAlwaysVisible() + else { // pip layer in gesture nav will disappear during transition - testSpec.assertLayers { - this.isVisible(pipApp.component) - .then().isInvisible(pipApp.component) - .then().isVisible(pipApp.component) + flicker.assertLayers { + this.isVisible(pipApp).then().isInvisible(pipApp).then().isVisible(pipApp) } } } + @Presubmit + @Test override fun pipLayerReduces() { // in gestural nav the pip enters through alpha animation - Assume.assumeFalse(testSpec.isGesturalNavigation) + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) super.pipLayerReduces() } + @Presubmit + @Test override fun focusChanges() { // in gestural nav the focus goes to different activity on swipe up - Assume.assumeFalse(testSpec.isGesturalNavigation) + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) super.focusChanges() } + @Presubmit + @Test + override fun entireScreenCovered() { + super.entireScreenCovered() + } + + @Presubmit + @Test override fun pipLayerRemainInsideVisibleBounds() { - if (!testSpec.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds() else { + if (!flicker.scenario.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds() + else { // pip layer in gesture nav will disappear during transition - testSpec.assertLayersStart { - this.visibleRegion(pipApp.component).coversAtMost(displayBounds) - } - testSpec.assertLayersEnd { - this.visibleRegion(pipApp.component).coversAtMost(displayBounds) - } + flicker.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) } + flicker.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) } } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt new file mode 100644 index 000000000000..e47805001cd0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** This test will fail because of b/264261596 */ +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class EnterPipOnUserLeaveHintTestCfArm(flicker: FlickerTest) : EnterPipOnUserLeaveHintTest(flicker) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index 61ac49835185..1524b16ebe59 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -17,14 +17,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import android.view.Surface import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -37,65 +36,50 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:EnterPipTest` * * Actions: + * ``` * Launch an app in full screen * Press an "enter pip" button to put [pipApp] in pip mode - * + * ``` * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited from [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +open class EnterPipTest(flicker: FlickerTest) : PipTransition(flicker) { - /** - * Defines the transition used to run the test - */ + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { - setupAndTeardown(this) setup { - eachRun { - pipApp.launchViaIntent(wmHelper) - } + pipApp.launchViaIntent(wmHelper) } teardown { - eachRun { - pipApp.exit(wmHelper) - } - } - transitions { - pipApp.clickEnterPipButton(wmHelper) + pipApp.exit(wmHelper) } + transitions { pipApp.clickEnterPipButton(wmHelper) } } - /** - * Checks [pipApp] window remains visible throughout the animation - */ + /** Checks [pipApp] window remains visible throughout the animation */ @Presubmit @Test - fun pipAppWindowAlwaysVisible() { - testSpec.assertWm { - this.isAppWindowVisible(pipApp.component) - } + open fun pipAppWindowAlwaysVisible() { + flicker.assertWm { this.isAppWindowVisible(pipApp) } } - /** - * Checks [pipApp] layer remains visible throughout the animation - */ + /** Checks [pipApp] layer remains visible throughout the animation */ @Presubmit @Test open fun pipAppLayerAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(pipApp.component) - } + flicker.assertLayers { this.isVisible(pipApp) } } /** @@ -105,9 +89,7 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec @Presubmit @Test fun pipWindowRemainInsideVisibleBounds() { - testSpec.assertWmVisibleRegion(pipApp.component) { - coversAtMost(displayBounds) - } + flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) } } /** @@ -117,79 +99,66 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec @Presubmit @Test open fun pipLayerRemainInsideVisibleBounds() { - testSpec.assertLayersVisibleRegion(pipApp.component) { - coversAtMost(displayBounds) - } + flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) } } - /** - * Checks that the visible region of [pipApp] always reduces during the animation - */ + /** Checks that the visible region of [pipApp] always reduces during the animation */ @Presubmit @Test open fun pipLayerReduces() { - val layerName = pipApp.component.toLayerName() - testSpec.assertLayers { - val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } + flicker.assertLayers { + val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } pipLayerList.zipWithNext { previous, current -> current.visibleRegion.notBiggerThan(previous.visibleRegion.region) } } } - /** - * Checks that [pipApp] window becomes pinned - */ + /** Checks that [pipApp] window becomes pinned */ @Presubmit @Test fun pipWindowBecomesPinned() { - testSpec.assertWm { - invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp.component) } + flicker.assertWm { + invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp) } .then() - .invoke("pipWindowIsPinned") { it.isPinned(pipApp.component) } + .invoke("pipWindowIsPinned") { it.isPinned(pipApp) } } } - /** - * Checks [LAUNCHER_COMPONENT] layer remains visible throughout the animation - */ + /** Checks [ComponentMatcher.LAUNCHER] layer remains visible throughout the animation */ @Presubmit @Test fun launcherLayerBecomesVisible() { - testSpec.assertLayers { - isInvisible(LAUNCHER_COMPONENT) + flicker.assertLayers { + isInvisible(ComponentNameMatcher.LAUNCHER) .then() - .isVisible(LAUNCHER_COMPONENT) + .isVisible(ComponentNameMatcher.LAUNCHER) } } /** - * Checks that the focus changes between the [pipApp] window and the launcher when - * closing the pip window + * Checks that the focus changes between the [pipApp] window and the launcher when closing the + * pip window */ @Presubmit @Test open fun focusChanges() { - testSpec.assertEventLog { - this.focusChanges(pipApp.`package`, "NexusLauncherActivity") - } + flicker.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") } } companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation + * and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 3 - ) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTestCfArm.kt new file mode 100644 index 000000000000..d2e864587431 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTestCfArm.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class EnterPipTestCfArm(flicker: FlickerTest) : EnterPipTest(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation + * and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index 7680f4dfa1d5..94a16da1c34b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -16,24 +16,23 @@ package com.android.wm.shell.flicker.pip +import android.app.Activity import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.entireScreenCovered +import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.helpers.FixedAppHelper +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP +import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION +import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT -import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION -import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP +import org.junit.Assume +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -46,71 +45,72 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest` * * Actions: + * ``` * Launch [testApp] on a fixed portrait orientation * Launch [pipApp] on a fixed landscape orientation * Broadcast action [ACTION_ENTER_PIP] to enter pip mode - * + * ``` * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -class EnterPipToOtherOrientationTest( - testSpec: FlickerTestParameter -) : PipTransition(testSpec) { - private val testApp = FixedAppHelper(instrumentation) - private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) - private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) +open class EnterPipToOtherOrientationTest(flicker: FlickerTest) : PipTransition(flicker) { + private val testApp = FixedOrientationAppHelper(instrumentation) + private val startingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_90) + private val endingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_0) - /** - * Defines the transition used to run the test - */ + /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit get() = { - setupAndTeardown(this) - setup { - eachRun { - // Launch a portrait only app on the fullscreen stack - testApp.launchViaIntent(wmHelper, stringExtras = mapOf( - EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())) - // Launch the PiP activity fixed as landscape - pipApp.launchViaIntent(wmHelper, stringExtras = mapOf( - EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())) - } + // Launch a portrait only app on the fullscreen stack + testApp.launchViaIntent( + wmHelper, + stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()) + ) + // Launch the PiP activity fixed as landscape + pipApp.launchViaIntent( + wmHelper, + stringExtras = + mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) + ) } teardown { - eachRun { - pipApp.exit(wmHelper) - testApp.exit(wmHelper) - } + pipApp.exit(wmHelper) + testApp.exit(wmHelper) } transitions { // Enter PiP, and assert that the PiP is within bounds now that the device is back // in portrait broadcastActionTrigger.doAction(ACTION_ENTER_PIP) - wmHelper.waitPipShown() - wmHelper.waitForAppTransitionIdle() // during rotation the status bar becomes invisible and reappears at the end - wmHelper.waitForNavBarStatusBarVisible() + wmHelper + .StateSyncBuilder() + .withPipShown() + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() } } /** - * Checks that the [FlickerComponentName.NAV_BAR] has the correct position at - * the start and end of the transition + * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to + * fix a orientation, Tablets instead keep the same orientation and add letterboxes */ - @FlakyTest - @Test - override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + @Before + fun setup() { + Assume.assumeFalse(tapl.isTablet) + } /** * Checks that all parts of the screen are covered at the start and end of the transition @@ -119,103 +119,85 @@ class EnterPipToOtherOrientationTest( */ @Presubmit @Test - override fun entireScreenCovered() = testSpec.entireScreenCovered(allStates = false) + fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered(allStates = false) - /** - * Checks [pipApp] window remains visible and on top throughout the transition - */ + /** Checks [pipApp] window remains visible and on top throughout the transition */ @Presubmit @Test fun pipAppWindowIsAlwaysOnTop() { - testSpec.assertWm { - isAppWindowOnTop(pipApp.component) - } + flicker.assertWm { isAppWindowOnTop(pipApp) } } - /** - * Checks that [testApp] window is not visible at the start - */ + /** Checks that [testApp] window is not visible at the start */ @Presubmit @Test fun testAppWindowInvisibleOnStart() { - testSpec.assertWmStart { - isAppWindowInvisible(testApp.component) - } + flicker.assertWmStart { isAppWindowInvisible(testApp) } } - /** - * Checks that [testApp] window is visible at the end - */ + /** Checks that [testApp] window is visible at the end */ @Presubmit @Test fun testAppWindowVisibleOnEnd() { - testSpec.assertWmEnd { - isAppWindowVisible(testApp.component) - } + flicker.assertWmEnd { isAppWindowVisible(testApp) } } - /** - * Checks that [testApp] layer is not visible at the start - */ + /** Checks that [testApp] layer is not visible at the start */ @Presubmit @Test fun testAppLayerInvisibleOnStart() { - testSpec.assertLayersStart { - isInvisible(testApp.component) - } + flicker.assertLayersStart { isInvisible(testApp) } } - /** - * Checks that [testApp] layer is visible at the end - */ + /** Checks that [testApp] layer is visible at the end */ @Presubmit @Test fun testAppLayerVisibleOnEnd() { - testSpec.assertLayersEnd { - isVisible(testApp.component) - } + flicker.assertLayersEnd { isVisible(testApp) } } /** - * Checks that the visible region of [pipApp] covers the full display area at the start of - * the transition + * Checks that the visible region of [pipApp] covers the full display area at the start of the + * transition */ @Presubmit @Test fun pipAppLayerCoversFullScreenOnStart() { - testSpec.assertLayersStart { - visibleRegion(pipApp.component).coversExactly(startingBounds) - } + flicker.assertLayersStart { visibleRegion(pipApp).coversExactly(startingBounds) } } /** - * Checks that the visible region of [testApp] plus the visible region of [pipApp] - * cover the full display area at the end of the transition + * Checks that the visible region of [testApp] plus the visible region of [pipApp] cover the + * full display area at the end of the transition */ @Presubmit @Test fun testAppPlusPipLayerCoversFullScreenOnEnd() { - testSpec.assertLayersEnd { - val pipRegion = visibleRegion(pipApp.component).region - visibleRegion(testApp.component) - .plus(pipRegion) - .coversExactly(endingBounds) + flicker.assertLayersEnd { + val pipRegion = visibleRegion(pipApp).region + visibleRegion(testApp).plus(pipRegion).coversExactly(endingBounds) } } + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTestCfArm.kt new file mode 100644 index 000000000000..39aab6ee49b7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTestCfArm.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** This test fails because of b/264261596 */ +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class EnterPipToOtherOrientationTestCfArm(flicker: FlickerTest) : + EnterPipToOtherOrientationTest(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt index 990872f58dc1..7466916a798a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt @@ -17,15 +17,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.wm.shell.flicker.helpers.FixedAppHelper +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.helpers.SimpleAppHelper import org.junit.Test -/** - * Base class for pip expand tests - */ -abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - protected val testApp = FixedAppHelper(instrumentation) +/** Base class for pip expand tests */ +abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flicker) { + protected val testApp = SimpleAppHelper(instrumentation) /** * Checks that the pip app window remains inside the display bounds throughout the whole @@ -34,9 +32,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans @Presubmit @Test open fun pipAppWindowRemainInsideVisibleBounds() { - testSpec.assertWmVisibleRegion(pipApp.component) { - coversAtMost(displayBounds) - } + flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) } } /** @@ -46,9 +42,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans @Presubmit @Test open fun pipAppLayerRemainInsideVisibleBounds() { - testSpec.assertLayersVisibleRegion(pipApp.component) { - coversAtMost(displayBounds) - } + flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) } } /** @@ -58,15 +52,15 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans @Presubmit @Test open fun showBothAppWindowsThenHidePip() { - testSpec.assertWm { + flicker.assertWm { // when the activity is STOPPING, sometimes it becomes invisible in an entry before // the window, sometimes in the same entry. This occurs because we log 1x per frame // thus we ignore activity here - isAppWindowVisible(testApp.component) - .isAppWindowOnTop(pipApp.component) - .then() - .isAppWindowInvisible(testApp.component) - .isAppWindowVisible(pipApp.component) + isAppWindowVisible(testApp) + .isAppWindowOnTop(pipApp) + .then() + .isAppWindowInvisible(testApp) + .isAppWindowVisible(pipApp) } } @@ -77,54 +71,46 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans @Presubmit @Test open fun showBothAppLayersThenHidePip() { - testSpec.assertLayers { - isVisible(testApp.component) - .isVisible(pipApp.component) - .then() - .isInvisible(testApp.component) - .isVisible(pipApp.component) + flicker.assertLayers { + isVisible(testApp).isVisible(pipApp).then().isInvisible(testApp).isVisible(pipApp) } } /** - * Checks that the visible region of [testApp] plus the visible region of [pipApp] - * cover the full display area at the start of the transition + * Checks that the visible region of [testApp] plus the visible region of [pipApp] cover the + * full display area at the start of the transition */ @Presubmit @Test open fun testPlusPipAppsCoverFullScreenAtStart() { - testSpec.assertLayersStart { - val pipRegion = visibleRegion(pipApp.component).region - visibleRegion(testApp.component) - .plus(pipRegion) - .coversExactly(displayBounds) + flicker.assertLayersStart { + val pipRegion = visibleRegion(pipApp).region + visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds) } } /** - * Checks that the visible region oft [pipApp] covers the full display area at the end of - * the transition + * Checks that the visible region oft [pipApp] covers the full display area at the end of the + * transition */ @Presubmit @Test open fun pipAppCoversFullScreenAtEnd() { - testSpec.assertLayersEnd { - visibleRegion(pipApp.component).coversExactly(displayBounds) - } + flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(displayBounds) } } - /** - * Checks that the visible region of [pipApp] always expands during the animation - */ + /** Checks that the visible region of [pipApp] always expands during the animation */ @Presubmit @Test open fun pipLayerExpands() { - val layerName = pipApp.component.toLayerName() - testSpec.assertLayers { - val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } + flicker.assertLayers { + val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } pipLayerList.zipWithNext { previous, current -> current.visibleRegion.coversAtLeast(previous.visibleRegion.region) } } } + + /** {@inheritDoc} */ + @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt index 0b4bc761838d..1b5c227d37e4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt @@ -17,36 +17,26 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import android.view.Surface -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER +import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Test -/** - * Base class for exiting pip (closing pip window) without returning to the app - */ -abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +/** Base class for exiting pip (closing pip window) without returning to the app */ +abstract class ExitPipTransition(flicker: FlickerTest) : PipTransition(flicker) { override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = true) { - setup { - eachRun { - this.setRotation(testSpec.startRotation) - } - } - teardown { - eachRun { - this.setRotation(Surface.ROTATION_0) - } - } + get() = buildTransition { + setup { this.setRotation(flicker.scenario.startRotation) } + teardown { this.setRotation(PlatformConsts.Rotation.ROTATION_0) } } /** - * Checks that [pipApp] window is pinned and visible at the start and then becomes - * unpinned and invisible at the same moment, and remains unpinned and invisible - * until the end of the transition + * Checks that [pipApp] window is pinned and visible at the start and then becomes unpinned and + * invisible at the same moment, and remains unpinned and invisible until the end of the + * transition */ @Presubmit @Test @@ -55,42 +45,36 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition // When Shell transition is enabled, we change the windowing mode at start, but // update the visibility after the transition is finished, so we can't check isNotPinned // and isAppWindowInvisible in the same assertion block. - testSpec.assertWm { + flicker.assertWm { this.invoke("hasPipWindow") { - it.isPinned(pipApp.component) - .isAppWindowVisible(pipApp.component) - .isAppWindowOnTop(pipApp.component) - }.then().invoke("!hasPipWindow") { - it.isNotPinned(pipApp.component) - .isAppWindowNotOnTop(pipApp.component) - } + it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp) + } + .then() + .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) } } - testSpec.assertWmEnd { isAppWindowInvisible(pipApp.component) } + flicker.assertWmEnd { isAppWindowInvisible(pipApp) } } else { - testSpec.assertWm { - this.invoke("hasPipWindow") { - it.isPinned(pipApp.component).isAppWindowVisible(pipApp.component) - }.then().invoke("!hasPipWindow") { - it.isNotPinned(pipApp.component).isAppWindowInvisible(pipApp.component) - } + flicker.assertWm { + this.invoke("hasPipWindow") { it.isPinned(pipApp).isAppWindowVisible(pipApp) } + .then() + .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowInvisible(pipApp) } } } } /** - * Checks that [pipApp] and [LAUNCHER_COMPONENT] layers are visible at the start - * of the transition. Then [pipApp] layer becomes invisible, and remains invisible - * until the end of the transition + * Checks that [pipApp] and [LAUNCHER] layers are visible at the start of the transition. Then + * [pipApp] layer becomes invisible, and remains invisible until the end of the transition */ @Presubmit @Test open fun pipLayerBecomesInvisible() { - testSpec.assertLayers { - this.isVisible(pipApp.component) - .isVisible(LAUNCHER_COMPONENT) + flicker.assertLayers { + this.isVisible(pipApp) + .isVisible(LAUNCHER) .then() - .isInvisible(pipApp.component) - .isVisible(LAUNCHER_COMPONENT) + .isInvisible(pipApp) + .isVisible(LAUNCHER) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt index 0768e82e491c..3bfcde3dbc48 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt @@ -16,14 +16,16 @@ package com.android.wm.shell.flicker.pip -import android.view.Surface -import androidx.test.filters.FlakyTest +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -36,65 +38,71 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest` * * Actions: + * ``` * Launch an app in pip mode [pipApp], * Launch another full screen mode [testApp] * Expand [pipApp] app to full screen by clicking on the pip window and * then on the expand button - * + * ``` * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -@FlakyTest(bugId = 219750830) -class ExitPipViaExpandButtonClickTest( - testSpec: FlickerTestParameter -) : ExitPipToAppTransition(testSpec) { +open class ExitPipViaExpandButtonClickTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) { - /** - * Defines the transition used to run the test - */ + /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = true) { + get() = buildTransition { setup { - eachRun { - // launch an app behind the pip one - testApp.launchViaIntent(wmHelper) - } + // launch an app behind the pip one + testApp.launchViaIntent(wmHelper) } transitions { // This will bring PipApp to fullscreen pipApp.expandPipWindowToApp(wmHelper) // Wait until the other app is no longer visible - wmHelper.waitForWindowSurfaceDisappeared(testApp.component) + wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify() } } - /** {@inheritDoc} */ + /** {@inheritDoc} */ @FlakyTest(bugId = 197726610) @Test - override fun pipLayerExpands() = super.pipLayerExpands() + override fun pipLayerExpands() { + Assume.assumeFalse(isShellTransitionsEnabled) + super.pipLayerExpands() + } + + @Presubmit + @Test + fun pipLayerExpands_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + super.pipLayerExpands() + } companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTestCfArm.kt new file mode 100644 index 000000000000..f77e335d8f52 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTestCfArm.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ExitPipViaExpandButtonClickTestCfArm(flicker: FlickerTest) : + ExitPipViaExpandButtonClickTest(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index c6a705dacb8d..dffbe7e8aef1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -16,16 +16,15 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -39,60 +38,61 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:ExitPipViaIntentTest` * * Actions: + * ``` * Launch an app in pip mode [pipApp], * Launch another full screen mode [testApp] * Expand [pipApp] app to full screen via an intent - * + * ``` * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited from [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) { +class ExitPipViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) { - /** - * Defines the transition used to run the test - */ + /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = true) { + get() = buildTransition { setup { - eachRun { - // launch an app behind the pip one - testApp.launchViaIntent(wmHelper) - } + // launch an app behind the pip one + testApp.launchViaIntent(wmHelper) } transitions { // This will bring PipApp to fullscreen pipApp.exitPipToFullScreenViaIntent(wmHelper) // Wait until the other app is no longer visible - wmHelper.waitForWindowSurfaceDisappeared(testApp.component) + wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify() } } - /** {@inheritDoc} */ - @FlakyTest(bugId = 206753786) + /** {@inheritDoc} */ + @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Presubmit @Test - override fun statusBarLayerRotatesScales() { + override fun statusBarLayerPositionAtStartAndEnd() { Assume.assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() + super.statusBarLayerPositionAtStartAndEnd() } @Presubmit @Test fun statusBarLayerRotatesScales_ShellTransit() { Assume.assumeTrue(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() + super.statusBarLayerPositionAtStartAndEnd() } - /** {@inheritDoc} */ + /** {@inheritDoc} */ @FlakyTest(bugId = 197726610) @Test override fun pipLayerExpands() { @@ -111,14 +111,15 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation + * and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt index 437ad893f1d9..232c025e60a9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt @@ -17,14 +17,12 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -37,63 +35,56 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:ExitPipWithDismissButtonTest` * * Actions: + * ``` * Launch an app in pip mode [pipApp], * Click on the pip window * Click on dismiss button and wait window disappear - * + * ``` * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) { +class ExitPipWithDismissButtonTest(flicker: FlickerTest) : ExitPipTransition(flicker) { override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) - transitions { - pipApp.closePipWindow(wmHelper) - } + transitions { pipApp.closePipWindow(wmHelper) } } - /** {@inheritDoc} */ - @FlakyTest(bugId = 206753786) - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - /** * Checks that the focus changes between the pip menu window and the launcher when clicking the * dismiss button on pip menu to close the pip window. */ @Presubmit @Test - fun focusDoesNotChange() { - testSpec.assertEventLog { - this.focusChanges("PipMenuView", "NexusLauncherActivity") - } + fun focusChanges() { + flicker.assertEventLog { this.focusChanges("PipMenuView", "NexusLauncherActivity") } } companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation + * and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 3) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 128703ad332c..dbbfdcc8803a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -17,14 +17,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -37,70 +36,78 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:ExitPipWithSwipeDownTest` * * Actions: + * ``` * Launch an app in pip mode [pipApp], * Swipe the pip window to the bottom-center of the screen and wait it disappear - * + * ``` * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) { +class ExitPipWithSwipeDownTest(flicker: FlickerTest) : ExitPipTransition(flicker) { override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) transitions { - val pipRegion = wmHelper.getWindowRegion(pipApp.component).bounds + val pipRegion = wmHelper.getWindowRegion(pipApp).bounds val pipCenterX = pipRegion.centerX() val pipCenterY = pipRegion.centerY() val displayCenterX = device.displayWidth / 2 - device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 10) - wmHelper.waitPipGone() - wmHelper.waitForWindowSurfaceDisappeared(pipApp.component) - wmHelper.waitForAppTransitionIdle() + val barComponent = + if (flicker.scenario.isTablet) { + ComponentNameMatcher.TASK_BAR + } else { + ComponentNameMatcher.NAV_BAR + } + val barLayerHeight = + wmHelper.currentState.layerState + .getLayerWithBuffer(barComponent) + ?.visibleRegion + ?.height + ?: error("Couldn't find Nav or Task bar layer") + // The dismiss button doesn't appear at the complete bottom of the screen, + val displayY = device.displayHeight - barLayerHeight + device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50) + // Wait until the other app is no longer visible + wmHelper + .StateSyncBuilder() + .withPipGone() + .withWindowSurfaceDisappeared(pipApp) + .withAppTransitionIdle() + .waitForAndVerify() } } - @FlakyTest - @Test - override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible() - - @FlakyTest - @Test - override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible() - - /** - * Checks that the focus doesn't change between windows during the transition - */ + /** Checks that the focus doesn't change between windows during the transition */ @Presubmit @Test fun focusDoesNotChange() { - testSpec.assertEventLog { - this.focusDoesNotChange() - } + flicker.assertEventLog { this.focusDoesNotChange() } } companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation + * and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 3) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index 28b7fc9bd29e..c90c2d42e9bf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -16,16 +16,14 @@ package com.android.wm.shell.flicker.pip -import androidx.test.filters.FlakyTest import android.platform.test.annotations.Presubmit -import android.view.Surface import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -38,29 +36,27 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest` * * Actions: + * ``` * Launch an app in pip mode [pipApp], * Expand [pipApp] app to its maximum pip size by double clicking on it - * + * ``` * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) { override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = true) { - transitions { - pipApp.doubleClickPipWindow(wmHelper) - } - } + get() = buildTransition { transitions { pipApp.doubleClickPipWindow(wmHelper) } } /** * Checks that the pip app window remains inside the display bounds throughout the whole @@ -69,9 +65,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition @Presubmit @Test fun pipWindowRemainInsideVisibleBounds() { - testSpec.assertWmVisibleRegion(pipApp.component) { - coversAtMost(displayBounds) - } + flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) } } /** @@ -81,42 +75,29 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition @Presubmit @Test fun pipLayerRemainInsideVisibleBounds() { - testSpec.assertLayersVisibleRegion(pipApp.component) { - coversAtMost(displayBounds) - } + flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) } } - /** - * Checks [pipApp] window remains visible throughout the animation - */ + /** Checks [pipApp] window remains visible throughout the animation */ @Presubmit @Test fun pipWindowIsAlwaysVisible() { - testSpec.assertWm { - isAppWindowVisible(pipApp.component) - } + flicker.assertWm { isAppWindowVisible(pipApp) } } - /** - * Checks [pipApp] layer remains visible throughout the animation - */ + /** Checks [pipApp] layer remains visible throughout the animation */ @Presubmit @Test fun pipLayerIsAlwaysVisible() { - testSpec.assertLayers { - isVisible(pipApp.component) - } + flicker.assertLayers { isVisible(pipApp) } } - /** - * Checks that the visible region of [pipApp] always expands during the animation - */ - @FlakyTest(bugId = 228012337) + /** Checks that the visible region of [pipApp] always expands during the animation */ + @Presubmit @Test fun pipLayerExpands() { - val layerName = pipApp.component.toLayerName() - testSpec.assertLayers { - val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } + flicker.assertLayers { + val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } pipLayerList.zipWithNext { previous, current -> current.visibleRegion.coversAtLeast(previous.visibleRegion.region) } @@ -126,65 +107,48 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition @Presubmit @Test fun pipSameAspectRatio() { - val layerName = pipApp.component.toLayerName() - testSpec.assertLayers { - val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } + flicker.assertLayers { + val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } pipLayerList.zipWithNext { previous, current -> current.visibleRegion.isSameAspectRatio(previous.visibleRegion) } } } - /** - * Checks [pipApp] window remains pinned throughout the animation - */ + /** Checks [pipApp] window remains pinned throughout the animation */ @Presubmit @Test fun windowIsAlwaysPinned() { - testSpec.assertWm { - this.invoke("hasPipWindow") { it.isPinned(pipApp.component) } - } + flicker.assertWm { this.invoke("hasPipWindow") { it.isPinned(pipApp) } } } - /** - * Checks [pipApp] layer remains visible throughout the animation - */ + /** Checks [ComponentNameMatcher.LAUNCHER] layer remains visible throughout the animation */ @Presubmit @Test fun launcherIsAlwaysVisible() { - testSpec.assertLayers { - isVisible(LAUNCHER_COMPONENT) - } + flicker.assertLayers { isVisible(ComponentNameMatcher.LAUNCHER) } } - /** - * Checks that the focus doesn't change between windows during the transition - */ - @FlakyTest(bugId = 216306753) + /** Checks that the focus doesn't change between windows during the transition */ + @Presubmit @Test fun focusDoesNotChange() { - testSpec.assertEventLog { - this.focusDoesNotChange() - } + flicker.assertEventLog { this.focusDoesNotChange() } } - @FlakyTest(bugId = 206753786) - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 3) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt new file mode 100644 index 000000000000..cb2326c85eb2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 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.pip + +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** Test expanding a pip window via pinch out gesture. */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) { + override val transition: FlickerBuilder.() -> Unit + get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30) } } + + /** Checks that the visible region area of [pipApp] always increases during the animation. */ + @Presubmit + @Test + fun pipLayerAreaIncreases() { + flicker.assertLayers { + val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } + pipLayerList.zipWithNext { previous, current -> + previous.visibleRegion.notBiggerThan(current.visibleRegion.region) + } + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt index 8729bb6776f0..16acc11f5729 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt @@ -16,15 +16,14 @@ package com.android.wm.shell.flicker.pip -import android.view.Surface -import androidx.test.filters.FlakyTest +import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.traces.region.RegionSubject +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.Direction import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -32,71 +31,62 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip movement with Launcher shelf height change (decrease). + * Test Pip movement with Launcher shelf height change (increase). * - * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest` + * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest` * * Actions: + * ``` * Launch [pipApp] in pip mode - * Launch [testApp] * Press home + * Launch [testApp] * Check if pip window moves down (visually) - * + * ``` * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -open class MovePipDownShelfHeightChangeTest( - testSpec: FlickerTestParameter -) : MovePipShelfHeightTransition(testSpec) { - /** - * Defines the transition used to run the test - */ +class MovePipDownShelfHeightChangeTest(flicker: FlickerTest) : + MovePipShelfHeightTransition(flicker) { + /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = false) { + get() = buildTransition { teardown { - eachRun { - testApp.launchViaIntent(wmHelper) - } - test { - testApp.exit(wmHelper) - } - } - transitions { - taplInstrumentation.pressHome() + tapl.pressHome() + testApp.exit(wmHelper) } + transitions { testApp.launchViaIntent(wmHelper) } } - override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) { - current.isHigherOrEqual(previous.region) - } + /** Checks that the visible region of [pipApp] window always moves down during the animation. */ + @Presubmit @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN) - /** {@inheritDoc} */ - @FlakyTest(bugId = 206753786) - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + /** Checks that the visible region of [pipApp] layer always moves down during the animation. */ + @Presubmit @Test fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN) companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt index 0499e7de9a0a..35525cbd4e1a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt @@ -17,46 +17,28 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper import com.android.server.wm.flicker.traces.region.RegionSubject -import com.android.wm.shell.flicker.helpers.FixedAppHelper +import com.android.wm.shell.flicker.Direction import org.junit.Test -/** - * Base class for pip tests with Launcher shelf height change - */ -abstract class MovePipShelfHeightTransition( - testSpec: FlickerTestParameter -) : PipTransition(testSpec) { - protected val taplInstrumentation = LauncherInstrumentation() - protected val testApp = FixedAppHelper(instrumentation) - - /** - * Checks if the window movement direction is valid - */ - protected abstract fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) +/** Base class for pip tests with Launcher shelf height change */ +abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransition(flicker) { + protected val testApp = FixedOrientationAppHelper(instrumentation) - /** - * Checks [pipApp] window remains visible throughout the animation - */ + /** Checks [pipApp] window remains visible throughout the animation */ @Presubmit @Test open fun pipWindowIsAlwaysVisible() { - testSpec.assertWm { - isAppWindowVisible(pipApp.component) - } + flicker.assertWm { isAppWindowVisible(pipApp) } } - /** - * Checks [pipApp] layer remains visible throughout the animation - */ + /** Checks [pipApp] layer remains visible throughout the animation */ @Presubmit @Test open fun pipLayerIsAlwaysVisible() { - testSpec.assertLayers { - isVisible(pipApp.component) - } + flicker.assertLayers { isVisible(pipApp) } } /** @@ -66,9 +48,7 @@ abstract class MovePipShelfHeightTransition( @Presubmit @Test open fun pipWindowRemainInsideVisibleBounds() { - testSpec.assertWmVisibleRegion(pipApp.component) { - coversAtMost(displayBounds) - } + flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) } } /** @@ -78,39 +58,49 @@ abstract class MovePipShelfHeightTransition( @Presubmit @Test open fun pipLayerRemainInsideVisibleBounds() { - testSpec.assertLayersVisibleRegion(pipApp.component) { - coversAtMost(displayBounds) - } + flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) } } /** - * Checks that the visible region of [pipApp] always moves in the correct direction + * Checks that the visible region of [pipApp] window always moves in the specified direction * during the animation. */ - @Presubmit - @Test - open fun pipWindowMoves() { - val windowName = pipApp.component.toWindowName() - testSpec.assertWm { - val pipWindowList = this.windowStates { it.name.contains(windowName) && it.isVisible } - pipWindowList.zipWithNext { previous, current -> - assertRegionMovement(previous.frame, current.frame) + protected fun pipWindowMoves(direction: Direction) { + flicker.assertWm { + val pipWindowFrameList = + this.windowStates { pipApp.windowMatchesAnyOf(it) && it.isVisible }.map { it.frame } + when (direction) { + Direction.UP -> assertRegionMovementUp(pipWindowFrameList) + Direction.DOWN -> assertRegionMovementDown(pipWindowFrameList) + else -> error("Unhandled direction") } } } /** - * Checks that the visible region of [pipApp] always moves up during the animation + * Checks that the visible region of [pipApp] layer always moves in the specified direction + * during the animation. */ - @Presubmit - @Test - open fun pipLayerMoves() { - val layerName = pipApp.component.toLayerName() - testSpec.assertLayers { - val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } - pipLayerList.zipWithNext { previous, current -> - assertRegionMovement(previous.visibleRegion, current.visibleRegion) + protected fun pipLayerMoves(direction: Direction) { + flicker.assertLayers { + val pipLayerRegionList = + this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } + .map { it.visibleRegion } + when (direction) { + Direction.UP -> assertRegionMovementUp(pipLayerRegionList) + Direction.DOWN -> assertRegionMovementDown(pipLayerRegionList) + else -> error("Unhandled direction") } } } -}
\ No newline at end of file + + private fun assertRegionMovementDown(regions: List<RegionSubject>) { + regions.zipWithNext { previous, current -> current.isLowerOrEqual(previous) } + regions.last().isLower(regions.first()) + } + + private fun assertRegionMovementUp(regions: List<RegionSubject>) { + regions.zipWithNext { previous, current -> current.isHigherOrEqual(previous.region) } + regions.last().isHigher(regions.first()) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt index 388b5e0b5e47..3a12a34a5206 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt @@ -16,18 +16,14 @@ package com.android.wm.shell.flicker.pip -import androidx.test.filters.FlakyTest -import android.platform.test.annotations.RequiresDevice -import android.view.Surface -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.traces.region.RegionSubject -import org.junit.Assume -import org.junit.Before +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.Direction import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -35,76 +31,61 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip movement with Launcher shelf height change (increase). + * Test Pip movement with Launcher shelf height change (decrease). * - * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest` + * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest` * * Actions: + * ``` * Launch [pipApp] in pip mode - * Press home * Launch [testApp] + * Press home * Check if pip window moves up (visually) - * + * ``` * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -class MovePipUpShelfHeightChangeTest( - testSpec: FlickerTestParameter -) : MovePipShelfHeightTransition(testSpec) { - @Before - fun before() { - Assume.assumeFalse(isShellTransitionsEnabled) - } - - /** - * Defines the transition used to run the test - */ +open class MovePipUpShelfHeightChangeTest(flicker: FlickerTest) : + MovePipShelfHeightTransition(flicker) { + /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = false) { - teardown { - eachRun { - taplInstrumentation.pressHome() - } - test { - testApp.exit(wmHelper) - } - } - transitions { - testApp.launchViaIntent(wmHelper) + get() = + buildTransition() { + setup { testApp.launchViaIntent(wmHelper) } + transitions { tapl.pressHome() } + teardown { testApp.exit(wmHelper) } } - } - override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) { - current.isLowerOrEqual(previous.region) - } + /** Checks that the visible region of [pipApp] window always moves up during the animation. */ + @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP) - /** {@inheritDoc} */ - @FlakyTest(bugId = 206753786) - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + /** Checks that the visible region of [pipApp] layer always moves up during the animation. */ + @Presubmit @Test fun pipLayerMovesUp() = pipLayerMoves(Direction.UP) companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index 1e30f6b83874..737e65c64f27 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -17,19 +17,17 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.FixMethodOrder @@ -38,17 +36,12 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -/** - * Test Pip launch. - * To run this test: `atest WMShellFlickerTests:PipKeyboardTest` - */ +/** Test Pip launch. To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -@FlakyTest(bugId = 218604389) -open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +open class PipKeyboardTest(flicker: FlickerTest) : PipTransition(flicker) { private val imeApp = ImeAppHelper(instrumentation) @Before @@ -56,19 +49,15 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS assumeFalse(isShellTransitionsEnabled) } + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = false) { + get() = buildTransition { setup { - test { - imeApp.launchViaIntent(wmHelper) - setRotation(testSpec.startRotation) - } + imeApp.launchViaIntent(wmHelper) + setRotation(flicker.scenario.startRotation) } teardown { - test { - imeApp.exit(wmHelper) - setRotation(Surface.ROTATION_0) - } + imeApp.exit(wmHelper) } transitions { // open the soft keyboard @@ -80,32 +69,21 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS } } - /** {@inheritDoc} */ - @FlakyTest(bugId = 206753786) - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - /** - * Ensure the pip window remains visible throughout any keyboard interactions - */ + /** Ensure the pip window remains visible throughout any keyboard interactions */ @Presubmit @Test open fun pipInVisibleBounds() { - testSpec.assertWmVisibleRegion(pipApp.component) { - val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) + flicker.assertWmVisibleRegion(pipApp) { + val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) coversAtMost(displayBounds) } } - /** - * Ensure that the pip window does not obscure the keyboard - */ + /** Ensure that the pip window does not obscure the keyboard */ @Presubmit @Test open fun pipIsAboveAppWindow() { - testSpec.assertWmTag(TAG_IME_VISIBLE) { - isAboveWindow(FlickerComponentName.IME, pipApp.component) - } + flicker.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(ComponentNameMatcher.IME, pipApp) } } companion object { @@ -113,10 +91,10 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt index fe51228230cb..901814e21971 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt @@ -18,10 +18,9 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.FlickerTest import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -34,8 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) { +class PipKeyboardTestShellTransit(flicker: FlickerTest) : PipKeyboardTest(flicker) { @Before override fun before() { @@ -44,5 +42,5 @@ class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardT @Presubmit @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() -}
\ No newline at end of file + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index 9fad4997e63a..4557a15222b9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -17,19 +17,14 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.entireScreenCovered +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.wm.shell.flicker.helpers.FixedAppHelper +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -42,146 +37,130 @@ import org.junit.runners.Parameterized * To run this test: `atest WMShellFlickerTests:PipRotationTest` * * Actions: + * ``` * Launch a [pipApp] in pip mode * Launch another app [fixedApp] (appears below pip) - * Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation] + * Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation] * (usually, 0->90 and 90->0) - * + * ``` * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited from [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - private val fixedApp = FixedAppHelper(instrumentation) - private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation) - private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation) +open class PipRotationTest(flicker: FlickerTest) : PipTransition(flicker) { + private val testApp = SimpleAppHelper(instrumentation) + private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) + private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation) override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = false) { + get() = buildTransition { setup { - test { - fixedApp.launchViaIntent(wmHelper) - } - eachRun { - setRotation(testSpec.startRotation) - } - } - transitions { - setRotation(testSpec.endRotation) + testApp.launchViaIntent(wmHelper) + setRotation(flicker.scenario.startRotation) } + transitions { setRotation(flicker.scenario.endRotation) } } - /** - * Checks that all parts of the screen are covered at the start and end of the transition - */ + /** Checks that [testApp] layer is within [screenBoundsStart] at the start of the transition */ @Presubmit @Test - override fun entireScreenCovered() = testSpec.entireScreenCovered() + fun fixedAppLayer_StartingBounds() { + flicker.assertLayersStart { visibleRegion(testApp).coversAtMost(screenBoundsStart) } + } - /** - * Checks the position of the navigation bar at the start and end of the transition - */ - @FlakyTest + /** Checks that [testApp] layer is within [screenBoundsEnd] at the end of the transition */ + @Presubmit @Test - override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + fun fixedAppLayer_EndingBounds() { + flicker.assertLayersEnd { visibleRegion(testApp).coversAtMost(screenBoundsEnd) } + } /** - * Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition + * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the start of the + * transition */ @Presubmit @Test - fun appLayerRotates_StartingBounds() { - testSpec.assertLayersStart { - visibleRegion(fixedApp.component).coversExactly(screenBoundsStart) + fun appLayers_StartingBounds() { + flicker.assertLayersStart { + visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsStart) } } /** - * Checks that [fixedApp] layer is within [screenBoundsEnd] at the end of the transition + * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the end of the + * transition */ @Presubmit @Test - fun appLayerRotates_EndingBounds() { - testSpec.assertLayersEnd { - visibleRegion(fixedApp.component).coversExactly(screenBoundsEnd) - } + fun appLayers_EndingBounds() { + flicker.assertLayersEnd { visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd) } } - /** - * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition - */ + /** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */ private fun pipLayerRotates_StartingBounds_internal() { - testSpec.assertLayersStart { - visibleRegion(pipApp.component).coversAtMost(screenBoundsStart) - } + flicker.assertLayersStart { visibleRegion(pipApp).coversAtMost(screenBoundsStart) } } - /** - * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition - */ + /** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */ @Presubmit @Test fun pipLayerRotates_StartingBounds() { pipLayerRotates_StartingBounds_internal() } - /** - * Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition - */ + /** Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition */ @Presubmit @Test fun pipLayerRotates_EndingBounds() { - testSpec.assertLayersEnd { - visibleRegion(pipApp.component).coversAtMost(screenBoundsEnd) - } + flicker.assertLayersEnd { visibleRegion(pipApp).coversAtMost(screenBoundsEnd) } } /** - * Ensure that the [pipApp] window does not obscure the [fixedApp] at the start of the - * transition + * Ensure that the [pipApp] window does not obscure the [testApp] at the start of the transition */ @Presubmit @Test fun pipIsAboveFixedAppWindow_Start() { - testSpec.assertWmStart { - isAboveWindow(pipApp.component, fixedApp.component) - } + flicker.assertWmStart { isAboveWindow(pipApp, testApp) } } /** - * Ensure that the [pipApp] window does not obscure the [fixedApp] at the end of the - * transition + * Ensure that the [pipApp] window does not obscure the [testApp] at the end of the transition */ @Presubmit @Test fun pipIsAboveFixedAppWindow_End() { - testSpec.assertWmEnd { - isAboveWindow(pipApp.component, fixedApp.component) - } + flicker.assertWmEnd { isAboveWindow(pipApp, testApp) } + } + + @Presubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() { + super.navBarLayerIsVisibleAtStartAndEnd() } companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation + * and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigRotationTests( - supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90), - repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.rotationTests() } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt deleted file mode 100644 index 7ba085d3cf1a..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2020 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.pip - -import com.android.wm.shell.flicker.FlickerTestBase -import com.android.wm.shell.flicker.helpers.PipAppHelper -import org.junit.Before - -abstract class PipTestBase( - rotationName: String, - rotation: Int -) : FlickerTestBase(rotationName, rotation) { - protected val testApp = PipAppHelper(instrumentation) - - @Before - override fun televisionSetUp() { - /** - * The super implementation assumes ([org.junit.Assume]) that not running on TV, thus - * disabling the test on TV. This test, however, *should run on TV*, so we overriding this - * method and simply leaving it blank. - */ - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index 8d542c8ec9e6..1bf1354f56aa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -18,33 +18,25 @@ package com.android.wm.shell.flicker.pip import android.app.Instrumentation import android.content.Intent -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.entireScreenCovered +import android.platform.test.annotations.Postsubmit +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.wm.shell.flicker.helpers.PipAppHelper -import com.android.wm.shell.flicker.testapp.Components +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.BaseTest +import com.google.common.truth.Truth import org.junit.Test -abstract class PipTransition(protected val testSpec: FlickerTestParameter) { - protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() +abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) { protected val pipApp = PipAppHelper(instrumentation) - protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) + protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) - protected abstract val transition: FlickerBuilder.() -> Unit + // Helper class to process test actions by broadcast. protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) { private fun createIntentWithAction(broadcastAction: String): Intent { @@ -52,123 +44,58 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { } fun doAction(broadcastAction: String) { - instrumentation.context - .sendBroadcast(createIntentWithAction(broadcastAction)) + instrumentation.context.sendBroadcast(createIntentWithAction(broadcastAction)) } companion object { // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - @JvmStatic - val ORIENTATION_LANDSCAPE = 0 + @JvmStatic val ORIENTATION_LANDSCAPE = 0 // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - @JvmStatic - val ORIENTATION_PORTRAIT = 1 - } - } - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - transition(this) + @JvmStatic val ORIENTATION_PORTRAIT = 1 } } /** - * Gets a configuration that handles basic setup and teardown of pip tests - */ - protected val setupAndTeardown: FlickerBuilder.() -> Unit - get() = { - setup { - test { - removeAllTasksButHome() - device.wakeUpAndGoToHomeScreen() - } - } - teardown { - eachRun { - setRotation(Surface.ROTATION_0) - } - test { - removeAllTasksButHome() - pipApp.exit(wmHelper) - } - } - } - - /** - * Gets a configuration that handles basic setup and teardown of pip tests and that - * launches the Pip app for test + * Gets a configuration that handles basic setup and teardown of pip tests and that launches the + * Pip app for test * - * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test) * @param stringExtras Arguments to pass to the PIP launch intent - * @param extraSpec Addicional segment of flicker specification + * @param extraSpec Additional segment of flicker specification */ @JvmOverloads protected open fun buildTransition( - eachRun: Boolean, - stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"), + stringExtras: Map<String, String> = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"), extraSpec: FlickerBuilder.() -> Unit = {} ): FlickerBuilder.() -> Unit { return { - setupAndTeardown(this) - setup { - test { - if (!eachRun) { - pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) - wmHelper.waitPipShown() - } - } - eachRun { - if (eachRun) { - pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) - wmHelper.waitPipShown() - } - } + setRotation(PlatformConsts.Rotation.ROTATION_0) + removeAllTasksButHome() + pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) } teardown { - eachRun { - if (eachRun) { - pipApp.exit(wmHelper) - } - } - test { - if (!eachRun) { - pipApp.exit(wmHelper) - } - } + pipApp.exit(wmHelper) } extraSpec(this) } } - @Presubmit + @Postsubmit @Test - open fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - open fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - open fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - open fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - - @Presubmit - @Test - open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @Presubmit - @Test - open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + fun hasAtMostOnePipDismissOverlayWindow() { + val matcher = ComponentNameMatcher("", "pip-dismiss-overlay") + flicker.assertWm { + val overlaysPerState = trace.entries.map { entry -> + entry.windowStates.count { window -> + matcher.windowMatchesAnyOf(window) + } <= 1 + } - @Presubmit - @Test - open fun entireScreenCovered() = testSpec.entireScreenCovered() -}
\ No newline at end of file + Truth.assertWithMessage("Number of dismiss overlays per state") + .that(overlaysPerState) + .doesNotContain(false) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 51339a1deb4b..871515ba9147 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -16,22 +16,22 @@ package com.android.wm.shell.flicker.pip +import android.app.Activity +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION +import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE -import com.android.wm.shell.flicker.testapp.Components -import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION +import org.junit.Assume +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -39,127 +39,122 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test exiting Pip with orientation changes. - * To run this test: `atest WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest` + * Test exiting Pip with orientation changes. To run this test: `atest + * WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -open class SetRequestedOrientationWhilePinnedTest( - testSpec: FlickerTestParameter -) : PipTransition(testSpec) { - private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) - private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) +open class SetRequestedOrientationWhilePinnedTest(flicker: FlickerTest) : PipTransition(flicker) { + private val startingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_0) + private val endingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_90) + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { setup { - test { - removeAllTasksButHome() - device.wakeUpAndGoToHomeScreen() - } - eachRun { - // Launch the PiP activity fixed as landscape. - pipApp.launchViaIntent(wmHelper, stringExtras = mapOf( - EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())) - // Enter PiP. - broadcastActionTrigger.doAction(Components.PipActivity.ACTION_ENTER_PIP) - wmHelper.waitPipShown() - wmHelper.waitForRotation(Surface.ROTATION_0) - wmHelper.waitForAppTransitionIdle() - // System bar may fade out during fixed rotation. - wmHelper.waitForNavBarStatusBarVisible() - } + // Launch the PiP activity fixed as landscape. + pipApp.launchViaIntent( + wmHelper, + stringExtras = + mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) + ) + // Enter PiP. + broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP) + // System bar may fade out during fixed rotation. + wmHelper + .StateSyncBuilder() + .withPipShown() + .withRotation(PlatformConsts.Rotation.ROTATION_0) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() } teardown { - eachRun { - pipApp.exit(wmHelper) - setRotation(Surface.ROTATION_0) - } - test { - removeAllTasksButHome() - } + pipApp.exit(wmHelper) } transitions { // Launch the activity back into fullscreen and ensure that it is now in landscape pipApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(pipApp.component) - wmHelper.waitForRotation(Surface.ROTATION_90) - wmHelper.waitForAppTransitionIdle() // System bar may fade out during fixed rotation. - wmHelper.waitForNavBarStatusBarVisible() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(pipApp) + .withRotation(PlatformConsts.Rotation.ROTATION_90) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() } } - @Presubmit - @Test - fun displayEndsAt90Degrees() { - testSpec.assertWmEnd { - hasRotation(Surface.ROTATION_90) - } + /** + * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to + * fix a orientation, Tablets instead keep the same orientation and add letterboxes + */ + @Before + fun setup() { + Assume.assumeFalse(tapl.isTablet) } @Presubmit @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - @Presubmit - @Test - override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() - - @FlakyTest - @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + fun displayEndsAt90Degrees() { + flicker.assertWmEnd { hasRotation(PlatformConsts.Rotation.ROTATION_90) } + } @Presubmit @Test fun pipWindowInsideDisplay() { - testSpec.assertWmStart { - frameRegion(pipApp.component).coversAtMost(startingBounds) - } + flicker.assertWmStart { visibleRegion(pipApp).coversAtMost(startingBounds) } } @Presubmit @Test fun pipAppShowsOnTop() { - testSpec.assertWmEnd { - isAppWindowOnTop(pipApp.component) - } + flicker.assertWmEnd { isAppWindowOnTop(pipApp) } } @Presubmit @Test fun pipLayerInsideDisplay() { - testSpec.assertLayersStart { - visibleRegion(pipApp.component).coversAtMost(startingBounds) - } + flicker.assertLayersStart { visibleRegion(pipApp).coversAtMost(startingBounds) } } @Presubmit @Test fun pipAlwaysVisible() { - testSpec.assertWm { - this.isAppWindowVisible(pipApp.component) - } + flicker.assertWm { this.isAppWindowVisible(pipApp) } } @Presubmit @Test fun pipAppLayerCoversFullScreen() { - testSpec.assertLayersEnd { - visibleRegion(pipApp.component).coversExactly(endingBounds) - } + flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(endingBounds) } } + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 264243884) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 1) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt new file mode 100644 index 000000000000..36909dd74245 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 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.pip.tv + +import android.app.Instrumentation +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.helpers.PipAppHelper +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper + +/** Helper class for PIP app on AndroidTV */ +open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) { + private val appSelector = By.pkg(`package`).depth(0) + + val ui: UiObject2? + get() = uiDevice.findObject(appSelector) + + private fun focusOnObject(selector: BySelector): Boolean { + // We expect all the focusable UI elements to be arranged in a way so that it is possible + // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top" + // from "the bottom". + repeat(FOCUS_ATTEMPTS) { + uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true } + ?: error("The object we try to focus on is gone.") + + uiDevice.pressDPadDown() + uiDevice.waitForIdle() + } + return false + } + + override fun clickObject(resId: String) { + val selector = By.res(`package`, resId) + focusOnObject(selector) || error("Could not focus on `$resId` object") + uiDevice.pressDPadCenter() + } + + @Deprecated( + "Use PipAppHelper.closePipWindow(wmHelper) instead", + ReplaceWith("closePipWindow(wmHelper)") + ) + override fun closePipWindow() { + uiDevice.closeTvPipWindow() + } + + /** Taps the pip window and dismisses it by clicking on the X button. */ + override fun closePipWindow(wmHelper: WindowManagerStateHelper) { + uiDevice.closeTvPipWindow() + + // Wait for animation to complete. + wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify() + } + + fun waitUntilClosed(): Boolean { + val appSelector = By.pkg(`package`).depth(0) + return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS) + } + + companion object { + private const val FOCUS_ATTEMPTS = 20 + private const val APP_CLOSE_WAIT_TIME_MS = 3_000L + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt index 9c50630095be..2cb18f948f0e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 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. @@ -14,47 +14,36 @@ * limitations under the License. */ -package com.android.wm.shell.flicker +package com.android.wm.shell.flicker.pip.tv import android.app.Instrumentation import android.content.pm.PackageManager -import android.content.pm.PackageManager.FEATURE_LEANBACK -import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice -import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.runners.Parameterized -/** - * Base class of all Flicker test that performs common functions for all flicker tests: - * - * - Caches transitions so that a transition is run once and the transition results are used by - * tests multiple times. This is needed for parameterized tests which call the BeforeClass methods - * multiple times. - * - Keeps track of all test artifacts and deletes ones which do not need to be reviewed. - * - Fails tests if results are not available for any test due to jank. - */ -abstract class FlickerTestBase( - protected val rotationName: String, - protected val rotation: Int -) { +abstract class PipTestBase(protected val rotationName: String, protected val rotation: Int) { val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() val uiDevice = UiDevice.getInstance(instrumentation) val packageManager: PackageManager = instrumentation.context.packageManager protected val isTelevision: Boolean by lazy { packageManager.run { - hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY) + hasSystemFeature(PackageManager.FEATURE_LEANBACK) || + hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY) } } + protected val testApp = PipAppHelperTv(instrumentation) - /** - * By default WmShellFlickerTests do not run on TV devices. - * If the test should run on TV - it should override this method. - */ @Before - open fun televisionSetUp() = assumeFalse(isTelevision) + open fun televisionSetUp() { + /** + * The super implementation assumes ([org.junit.Assume]) that not running on TV, thus + * disabling the test on TV. This test, however, *should run on TV*, so we overriding this + * method and simply leaving it blank. + */ + } companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt index 49094e609fbc..8a073abf032c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt @@ -25,16 +25,11 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -/** - * Test Pip Menu on TV. - * To run this test: `atest WMShellFlickerTests:TvPipBasicTest` - */ +/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipBasicTest` */ @RequiresDevice @RunWith(Parameterized::class) -class TvPipBasicTest( - private val radioButtonId: String, - private val pipWindowRatio: Rational? -) : TvPipTestBase() { +class TvPipBasicTest(private val radioButtonId: String, private val pipWindowRatio: Rational?) : + TvPipTestBase() { @Test fun enterPip_openMenu_pressBack_closePip() { @@ -43,10 +38,10 @@ class TvPipBasicTest( // Set up ratio and enter Pip testApp.clickObject(radioButtonId) - testApp.clickEnterPipButton() + testApp.clickEnterPipButton(wmHelper) - val actualRatio: Float = testApp.ui?.visibleBounds?.ratio - ?: fail("Application UI not found") + val actualRatio: Float = + testApp.ui?.visibleBounds?.ratio ?: fail("Application UI not found") pipWindowRatio?.let { expectedRatio -> assertEquals("Wrong Pip window ratio", expectedRatio.toFloat(), actualRatio) } @@ -62,7 +57,8 @@ class TvPipBasicTest( // Make sure Pip Window ration remained the same after Pip menu was closed testApp.ui?.visibleBounds?.let { newBounds -> assertEquals("Pip window ratio has changed", actualRatio, newBounds.ratio) - } ?: fail("Application UI not found") + } + ?: fail("Application UI not found") // Close Pip testApp.closePipWindow() @@ -77,11 +73,11 @@ class TvPipBasicTest( fun getParams(): Collection<Array<Any?>> { infix fun Int.to(denominator: Int) = Rational(this, denominator) return listOf( - arrayOf("ratio_default", null), - arrayOf("ratio_square", 1 to 1), - arrayOf("ratio_wide", 2 to 1), - arrayOf("ratio_tall", 1 to 2) + arrayOf("ratio_default", null), + arrayOf("ratio_square", 1 to 1), + arrayOf("ratio_wide", 2 to 1), + arrayOf("ratio_tall", 1 to 2) ) } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt index 061218a015e4..0432a8497fbe 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt @@ -19,35 +19,37 @@ package com.android.wm.shell.flicker.pip.tv import android.graphics.Rect import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.UiObject2 +import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME -import com.android.wm.shell.flicker.testapp.Components import com.android.wm.shell.flicker.wait import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -/** - * Test Pip Menu on TV. - * To run this test: `atest WMShellFlickerTests:TvPipMenuTests` - */ +/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipMenuTests` */ @RequiresDevice class TvPipMenuTests : TvPipTestBase() { - private val systemUiResources = - packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME) - private val pipBoundsWhileInMenu: Rect = systemUiResources.run { - val bounds = getString(getIdentifier("pip_menu_bounds", "string", - SYSTEM_UI_PACKAGE_NAME)) - Rect.unflattenFromString(bounds) ?: error("Could not retrieve PiP menu bounds") + private val systemUiResources by lazy { + packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME) + } + private val pipBoundsWhileInMenu: Rect by lazy { + systemUiResources.run { + val bounds = + getString(getIdentifier("pip_menu_bounds", "string", SYSTEM_UI_PACKAGE_NAME)) + Rect.unflattenFromString(bounds) ?: error("Could not retrieve PiP menu bounds") + } } - private val playButtonDescription = systemUiResources.run { - getString(getIdentifier("pip_play", "string", - SYSTEM_UI_PACKAGE_NAME)) + private val playButtonDescription by lazy { + systemUiResources.run { + getString(getIdentifier("pip_play", "string", SYSTEM_UI_PACKAGE_NAME)) + } } - private val pauseButtonDescription = systemUiResources.run { - getString(getIdentifier("pip_pause", "string", - SYSTEM_UI_PACKAGE_NAME)) + private val pauseButtonDescription by lazy { + systemUiResources.run { + getString(getIdentifier("pip_pause", "string", SYSTEM_UI_PACKAGE_NAME)) + } } @Before @@ -61,20 +63,29 @@ class TvPipMenuTests : TvPipTestBase() { enterPip_openMenu_assertShown() // Make sure the PiP task is positioned where it should be. - val activityBounds: Rect = testApp.ui?.visibleBounds - ?: error("Could not retrieve Pip Activity bounds") - assertTrue("Pip Activity is positioned correctly while Pip menu is shown", - pipBoundsWhileInMenu == activityBounds) + val activityBounds: Rect = + testApp.ui?.visibleBounds ?: error("Could not retrieve Pip Activity bounds") + assertTrue( + "Pip Activity is positioned correctly while Pip menu is shown", + pipBoundsWhileInMenu == activityBounds + ) // Make sure the Pip Menu Actions are positioned correctly. uiDevice.findTvPipMenuControls()?.visibleBounds?.run { - assertTrue("Pip Menu Actions should be positioned below the Activity in Pip", - top >= activityBounds.bottom) - assertTrue("Pip Menu Actions should be positioned central horizontally", - centerX() == uiDevice.displayWidth / 2) - assertTrue("Pip Menu Actions should be fully shown on the screen", - left >= 0 && right <= uiDevice.displayWidth && bottom <= uiDevice.displayHeight) - } ?: error("Could not retrieve Pip Menu Actions bounds") + assertTrue( + "Pip Menu Actions should be positioned below the Activity in Pip", + top >= activityBounds.bottom + ) + assertTrue( + "Pip Menu Actions should be positioned central horizontally", + centerX() == uiDevice.displayWidth / 2 + ) + assertTrue( + "Pip Menu Actions should be fully shown on the screen", + left >= 0 && right <= uiDevice.displayWidth && bottom <= uiDevice.displayHeight + ) + } + ?: error("Could not retrieve Pip Menu Actions bounds") testApp.closePipWindow() } @@ -107,7 +118,7 @@ class TvPipMenuTests : TvPipTestBase() { // PiP menu should contain the Close button uiDevice.findTvPipMenuCloseButton() - ?: fail("\"Close PIP\" button should be shown in Pip menu") + ?: fail("\"Close PIP\" button should be shown in Pip menu") // Clicking on the Close button should close the app uiDevice.clickTvPipMenuCloseButton() @@ -120,13 +131,15 @@ class TvPipMenuTests : TvPipTestBase() { // PiP menu should contain the Fullscreen button uiDevice.findTvPipMenuFullscreenButton() - ?: fail("\"Full screen\" button should be shown in Pip menu") + ?: fail("\"Full screen\" button should be shown in Pip menu") // Clicking on the fullscreen button should return app to the fullscreen mode. // Click, wait for the app to go fullscreen uiDevice.clickTvPipMenuFullscreenButton() - assertTrue("\"Full screen\" button should open the app fullscreen", - wait { testApp.ui?.isFullscreen(uiDevice) ?: false }) + assertTrue( + "\"Full screen\" button should open the app fullscreen", + wait { testApp.ui?.isFullscreen(uiDevice) ?: false } + ) // Close the app uiDevice.pressBack() @@ -143,8 +156,10 @@ class TvPipMenuTests : TvPipTestBase() { // PiP menu should contain the Pause button uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription) - ?: fail("\"Pause\" button should be shown in Pip menu if there is an active " + - "playing media session.") + ?: fail( + "\"Pause\" button should be shown in Pip menu if there is an active " + + "playing media session." + ) // When we pause media, the button should change from Pause to Play uiDevice.clickTvPipMenuElementWithDescription(pauseButtonDescription) @@ -152,8 +167,10 @@ class TvPipMenuTests : TvPipTestBase() { assertFullscreenAndCloseButtonsAreShown() // PiP menu should contain the Play button now uiDevice.waitForTvPipMenuElementWithDescription(playButtonDescription) - ?: fail("\"Play\" button should be shown in Pip menu if there is an active " + - "paused media session.") + ?: fail( + "\"Play\" button should be shown in Pip menu if there is an active " + + "paused media session." + ) testApp.closePipWindow() } @@ -165,44 +182,47 @@ class TvPipMenuTests : TvPipTestBase() { enterPip_openMenu_assertShown() // PiP menu should contain "No-Op", "Off" and "Clear" buttons... - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP) - ?: fail("\"No-Op\" button should be shown in Pip menu") - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF) - ?: fail("\"Off\" button should be shown in Pip menu") - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR) - ?: fail("\"Clear\" button should be shown in Pip menu") + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP) + ?: fail("\"No-Op\" button should be shown in Pip menu") + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF) + ?: fail("\"Off\" button should be shown in Pip menu") + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) + ?: fail("\"Clear\" button should be shown in Pip menu") // ... and should also contain the "Full screen" and "Close" buttons. assertFullscreenAndCloseButtonsAreShown() - uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF) + uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF) // Invoking the "Off" action should replace it with the "On" action/button and should // remove the "No-Op" action/button. "Clear" action/button should remain in the menu ... - uiDevice.waitForTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_ON) - ?: fail("\"On\" button should be shown in Pip for a corresponding custom action") - assertNull("\"No-Op\" button should not be shown in Pip menu", - uiDevice.findTvPipMenuElementWithDescription( - Components.PipActivity.MENU_ACTION_NO_OP)) - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR) - ?: fail("\"Clear\" button should be shown in Pip menu") + uiDevice.waitForTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_ON) + ?: fail("\"On\" button should be shown in Pip for a corresponding custom action") + assertNull( + "\"No-Op\" button should not be shown in Pip menu", + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP) + ) + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) + ?: fail("\"Clear\" button should be shown in Pip menu") // ... as well as the "Full screen" and "Close" buttons. assertFullscreenAndCloseButtonsAreShown() - uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR) + uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) // Invoking the "Clear" action should remove all the custom actions and their corresponding // buttons, ... - uiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone( - Components.PipActivity.MENU_ACTION_ON)?.also { - isGone -> if (!isGone) fail("\"On\" button should not be shown in Pip menu") - } - assertNull("\"Off\" button should not be shown in Pip menu", - uiDevice.findTvPipMenuElementWithDescription( - Components.PipActivity.MENU_ACTION_OFF)) - assertNull("\"Clear\" button should not be shown in Pip menu", - uiDevice.findTvPipMenuElementWithDescription( - Components.PipActivity.MENU_ACTION_CLEAR)) - assertNull("\"No-Op\" button should not be shown in Pip menu", - uiDevice.findTvPipMenuElementWithDescription( - Components.PipActivity.MENU_ACTION_NO_OP)) + uiDevice + .waitUntilTvPipMenuElementWithDescriptionIsGone(ActivityOptions.Pip.MENU_ACTION_ON) + ?.also { isGone -> if (!isGone) fail("\"On\" button should not be shown in Pip menu") } + assertNull( + "\"Off\" button should not be shown in Pip menu", + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF) + ) + assertNull( + "\"Clear\" button should not be shown in Pip menu", + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) + ) + assertNull( + "\"No-Op\" button should not be shown in Pip menu", + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP) + ) // ... but the menu should still contain the "Full screen" and "Close" buttons. assertFullscreenAndCloseButtonsAreShown() @@ -217,26 +237,32 @@ class TvPipMenuTests : TvPipTestBase() { enterPip_openMenu_assertShown() // PiP menu should contain "No-Op", "Off" and "Clear" buttons for the custom actions... - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP) - ?: fail("\"No-Op\" button should be shown in Pip menu") - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF) - ?: fail("\"Off\" button should be shown in Pip menu") - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR) - ?: fail("\"Clear\" button should be shown in Pip menu") + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP) + ?: fail("\"No-Op\" button should be shown in Pip menu") + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF) + ?: fail("\"Off\" button should be shown in Pip menu") + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) + ?: fail("\"Clear\" button should be shown in Pip menu") // ... should also contain the "Full screen" and "Close" buttons, ... assertFullscreenAndCloseButtonsAreShown() // ... but should not contain media buttons. - assertNull("\"Play\" button should not be shown in menu when there are custom actions", - uiDevice.findTvPipMenuElementWithDescription(playButtonDescription)) - assertNull("\"Pause\" button should not be shown in menu when there are custom actions", - uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription)) - - uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR) + assertNull( + "\"Play\" button should not be shown in menu when there are custom actions", + uiDevice.findTvPipMenuElementWithDescription(playButtonDescription) + ) + assertNull( + "\"Pause\" button should not be shown in menu when there are custom actions", + uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription) + ) + + uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) // Invoking the "Clear" action should remove all the custom actions, which should bring up // media buttons... uiDevice.waitForTvPipMenuElementWithDescription(pauseButtonDescription) - ?: fail("\"Pause\" button should be shown in Pip menu if there is an active " + - "playing media session.") + ?: fail( + "\"Pause\" button should be shown in Pip menu if there is an active " + + "playing media session." + ) // ... while the "Full screen" and "Close" buttons should remain in the menu. assertFullscreenAndCloseButtonsAreShown() @@ -244,7 +270,7 @@ class TvPipMenuTests : TvPipTestBase() { } private fun enterPip_openMenu_assertShown(): UiObject2 { - testApp.clickEnterPipButton() + testApp.clickEnterPipButton(wmHelper) // Pressing the Window key should bring up Pip menu uiDevice.pressWindowKey() return uiDevice.waitForTvPipMenu() ?: fail("Pip menu should have been shown") @@ -252,8 +278,8 @@ class TvPipMenuTests : TvPipTestBase() { private fun assertFullscreenAndCloseButtonsAreShown() { uiDevice.findTvPipMenuCloseButton() - ?: fail("\"Close PIP\" button should be shown in Pip menu") + ?: fail("\"Close PIP\" button should be shown in Pip menu") uiDevice.findTvPipMenuFullscreenButton() - ?: fail("\"Full screen\" button should be shown in Pip menu") + ?: fail("\"Full screen\" button should be shown in Pip menu") } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt index bcf38d340867..90406c510bad 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt @@ -34,8 +34,8 @@ import org.junit.Before import org.junit.Test /** - * Test Pip Notifications on TV. - * To run this test: `atest WMShellFlickerTests:TvPipNotificationTests` + * Test Pip Notifications on TV. To run this test: `atest + * WMShellFlickerTests:TvPipNotificationTests` */ @RequiresDevice class TvPipNotificationTests : TvPipTestBase() { @@ -56,49 +56,58 @@ class TvPipNotificationTests : TvPipTestBase() { @Test fun pipNotification_postedAndDismissed() { testApp.launchViaIntent() - testApp.clickEnterPipButton() + testApp.clickEnterPipButton(wmHelper) - assertNotNull("Pip notification should have been posted", - waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) }) + assertNotNull( + "Pip notification should have been posted", + waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) } + ) testApp.closePipWindow() - assertTrue("Pip notification should have been dismissed", - waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) }) + assertTrue( + "Pip notification should have been dismissed", + waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) } + ) } @Test fun pipNotification_closeIntent() { testApp.launchViaIntent() - testApp.clickEnterPipButton() - - val notification: StatusBarNotification = waitForNotificationToAppear { - it.isPipNotificationWithTitle(testApp.appName) - } ?: fail("Pip notification should have been posted") - - notification.deleteIntent?.send() - ?: fail("Pip notification should contain `delete_intent`") - - assertTrue("Pip should have closed by sending the `delete_intent`", - testApp.waitUntilClosed()) - assertTrue("Pip notification should have been dismissed", - waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) }) + testApp.clickEnterPipButton(wmHelper) + + val notification: StatusBarNotification = + waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) } + ?: fail("Pip notification should have been posted") + + notification.deleteIntent?.send() ?: fail("Pip notification should contain `delete_intent`") + + assertTrue( + "Pip should have closed by sending the `delete_intent`", + testApp.waitUntilClosed() + ) + assertTrue( + "Pip notification should have been dismissed", + waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) } + ) } @Test fun pipNotification_menuIntent() { - testApp.launchViaIntent() - testApp.clickEnterPipButton() + testApp.launchViaIntent(wmHelper) + testApp.clickEnterPipButton(wmHelper) - val notification: StatusBarNotification = waitForNotificationToAppear { - it.isPipNotificationWithTitle(testApp.appName) - } ?: fail("Pip notification should have been posted") + val notification: StatusBarNotification = + waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) } + ?: fail("Pip notification should have been posted") notification.contentIntent?.send() ?: fail("Pip notification should contain `content_intent`") - assertNotNull("Pip menu should have been shown after sending `content_intent`", - uiDevice.waitForTvPipMenu()) + assertNotNull( + "Pip menu should have been shown after sending `content_intent`", + uiDevice.waitForTvPipMenu() + ) uiDevice.pressBack() testApp.closePipWindow() @@ -106,41 +115,44 @@ class TvPipNotificationTests : TvPipTestBase() { @Test fun pipNotification_mediaSessionTitle_isDisplayed() { - testApp.launchViaIntent() + testApp.launchViaIntent(wmHelper) // Start media session and to PiP testApp.clickStartMediaSessionButton() - testApp.clickEnterPipButton() + testApp.clickEnterPipButton(wmHelper) // Wait for the correct notification to show up... - waitForNotificationToAppear { - it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) - } ?: fail("Pip notification with media session title should have been posted") + waitForNotificationToAppear { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) } + ?: fail("Pip notification with media session title should have been posted") // ... and make sure "regular" PiP notification is now shown - assertNull("Regular notification should not have been posted", - findNotification { it.isPipNotificationWithTitle(testApp.appName) }) + assertNull( + "Regular notification should not have been posted", + findNotification { it.isPipNotificationWithTitle(testApp.appName) } + ) // Pause the media session. When paused the application updates the title for the media // session. This change should be reflected in the notification. testApp.pauseMedia() // Wait for the "paused" notification to show up... - waitForNotificationToAppear { - it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) - } ?: fail("Pip notification with media session title should have been posted") + waitForNotificationToAppear { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) } + ?: fail("Pip notification with media session title should have been posted") // ... and make sure "playing" PiP notification is gone - assertNull("Regular notification should not have been posted", - findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) }) + assertNull( + "Regular notification should not have been posted", + findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) } + ) // Now stop the media session, which should revert the title to the "default" one. testApp.stopMedia() // Wait for the "regular" notification to show up... - waitForNotificationToAppear { - it.isPipNotificationWithTitle(testApp.appName) - } ?: fail("Pip notification with media session title should have been posted") + waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) } + ?: fail("Pip notification with media session title should have been posted") // ... and make sure previous ("paused") notification is gone - assertNull("Regular notification should not have been posted", - findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) }) + assertNull( + "Regular notification should not have been posted", + findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) } + ) testApp.closePipWindow() } @@ -170,4 +182,4 @@ private val StatusBarNotification.deleteIntent: PendingIntent? get() = tvExtensions?.getParcelable("delete_intent") private fun StatusBarNotification.isPipNotificationWithTitle(expectedTitle: String): Boolean = - tag == "TvPip" && title == expectedTitle
\ No newline at end of file + tag == "TvPip" && title == expectedTitle diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt index 9c3b0fa183b6..dc1fe4761757 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt @@ -23,8 +23,8 @@ import android.os.SystemClock import android.view.Surface.ROTATION_0 import android.view.Surface.rotationToString import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME -import com.android.wm.shell.flicker.pip.PipTestBase import org.junit.After import org.junit.Assert.assertFalse import org.junit.Assume.assumeTrue @@ -33,6 +33,7 @@ import org.junit.Before abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATION_0) { private val systemUiProcessObserver = SystemUiProcessObserver() + protected val wmHelper = WindowManagerStateHelper() @Before final override fun televisionSetUp() { @@ -67,7 +68,8 @@ abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATIO fun start() { hasDied = false uiAutomation.adoptShellPermissionIdentity( - android.Manifest.permission.SET_ACTIVITY_WATCHER) + android.Manifest.permission.SET_ACTIVITY_WATCHER + ) activityManager.registerProcessObserver(this) } @@ -88,4 +90,4 @@ abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATIO companion object { private const val AFTER_TEXT_PROCESS_CHECK_DELAY = 1_000L // 1 sec } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt index 1c663409b913..b0adbe1d07ce 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt @@ -33,32 +33,31 @@ private const val TV_PIP_MENU_FULLSCREEN_BUTTON_ID = "tv_pip_menu_fullscreen_but private const val FOCUS_ATTEMPTS = 10 private const val WAIT_TIME_MS = 3_000L -private val TV_PIP_MENU_SELECTOR = - By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_ROOT_ID) +private val TV_PIP_MENU_SELECTOR = By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_ROOT_ID) private val TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR = - By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_BUTTONS_CONTAINER_ID) + By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_BUTTONS_CONTAINER_ID) private val TV_PIP_MENU_CLOSE_BUTTON_SELECTOR = - By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_CLOSE_BUTTON_ID) + By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_CLOSE_BUTTON_ID) private val TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR = - By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_FULLSCREEN_BUTTON_ID) + By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_FULLSCREEN_BUTTON_ID) fun UiDevice.waitForTvPipMenu(): UiObject2? = - wait(Until.findObject(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS) + wait(Until.findObject(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS) fun UiDevice.waitForTvPipMenuToClose(): Boolean = - wait(Until.gone(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS) + wait(Until.gone(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS) fun UiDevice.findTvPipMenuControls(): UiObject2? = - findTvPipMenuElement(TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR) + findTvPipMenuElement(TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR) fun UiDevice.findTvPipMenuCloseButton(): UiObject2? = - findTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) + findTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) fun UiDevice.findTvPipMenuFullscreenButton(): UiObject2? = - findTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) + findTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) fun UiDevice.findTvPipMenuElementWithDescription(desc: String): UiObject2? = - findTvPipMenuElement(By.desc(desc)) + findTvPipMenuElement(By.desc(desc)) private fun UiDevice.findTvPipMenuElement(selector: BySelector): UiObject2? = findObject(TV_PIP_MENU_SELECTOR)?.findObject(selector) @@ -70,11 +69,10 @@ fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? { // descendant and then retrieve the element from the menu and return to the caller of this // method. val elementSelector = By.desc(desc) - val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR) - .hasDescendant(elementSelector) + val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR).hasDescendant(elementSelector) return wait(Until.findObject(menuContainingElementSelector), WAIT_TIME_MS) - ?.findObject(elementSelector) + ?.findObject(elementSelector) } fun UiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(desc: String): Boolean? { @@ -86,18 +84,17 @@ fun UiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(desc: String): Boole fun UiDevice.clickTvPipMenuCloseButton() { focusOnAndClickTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) || - error("Could not focus on the Close button") + error("Could not focus on the Close button") } fun UiDevice.clickTvPipMenuFullscreenButton() { focusOnAndClickTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) || - error("Could not focus on the Fullscreen button") + error("Could not focus on the Fullscreen button") } fun UiDevice.clickTvPipMenuElementWithDescription(desc: String) { - focusOnAndClickTvPipMenuElement(By.desc(desc) - .pkg(SYSTEM_UI_PACKAGE_NAME)) || - error("Could not focus on the Pip menu object with \"$desc\" description") + focusOnAndClickTvPipMenuElement(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME)) || + error("Could not focus on the Pip menu object with \"$desc\" description") // So apparently Accessibility framework on TV is not very reliable and sometimes the state of // the tree of accessibility nodes as seen by the accessibility clients kind of lags behind of // the "real" state of the "UI tree". It seems, however, that moving focus around the tree @@ -110,7 +107,8 @@ fun UiDevice.clickTvPipMenuElementWithDescription(desc: String) { private fun UiDevice.focusOnAndClickTvPipMenuElement(selector: BySelector): Boolean { repeat(FOCUS_ATTEMPTS) { - val element = findTvPipMenuElement(selector) + val element = + findTvPipMenuElement(selector) ?: error("The Pip Menu element we try to focus on is gone.") if (element.isFocusedOrHasFocusedChild) { @@ -119,10 +117,11 @@ private fun UiDevice.focusOnAndClickTvPipMenuElement(selector: BySelector): Bool } findTvPipMenuElement(By.focused(true))?.let { focused -> - if (element.visibleCenter.x < focused.visibleCenter.x) - pressDPadLeft() else pressDPadRight() + if (element.visibleCenter.x < focused.visibleCenter.x) pressDPadLeft() + else pressDPadRight() waitForIdle() - } ?: error("Pip menu does not contain a focused element") + } + ?: error("Pip menu does not contain a focused element") } return false @@ -155,9 +154,8 @@ private fun UiDevice.moveFocus() { fun UiDevice.pressWindowKey() = pressKeyCode(KeyEvent.KEYCODE_WINDOW) -fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = visibleBounds.run { - height() == uiDevice.displayHeight && width() == uiDevice.displayWidth -} +fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = + visibleBounds.run { height() == uiDevice.displayHeight && width() == uiDevice.displayWidth } val UiObject2.isFocusedOrHasFocusedChild: Boolean get() = isFocused || findObject(By.focused(true)) != null diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt new file mode 100644 index 000000000000..c08ad697b6ac --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.common.EdgeExtensionComponentMatcher +import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.appWindowIsVisibleAtStart +import com.android.wm.shell.flicker.appWindowKeepVisible +import com.android.wm.shell.flicker.layerKeepVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible +import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test copy content from the left to the right side of the split-screen. + * + * To run this test: `atest WMShellFlickerTests:CopyContentInSplit` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class CopyContentInSplit(flicker: FlickerTest) : SplitScreenBase(flicker) { + private val textEditApp = SplitScreenUtils.getIme(instrumentation) + private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#") + private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:") + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) } + transitions { + SplitScreenUtils.copyContentInSplit( + instrumentation, + device, + primaryApp, + textEditApp + ) + } + } + + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() { + flicker.appWindowIsVisibleAtStart(primaryApp) + flicker.appWindowIsVisibleAtStart(textEditApp) + flicker.splitScreenDividerIsVisibleAtStart() + + flicker.appWindowIsVisibleAtEnd(primaryApp) + flicker.appWindowIsVisibleAtEnd(textEditApp) + flicker.splitScreenDividerIsVisibleAtEnd() + + // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit() + } + + @Presubmit + @Test + fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + + @Presubmit @Test fun primaryAppLayerKeepVisible() = flicker.layerKeepVisible(primaryApp) + + @Presubmit @Test fun textEditAppLayerKeepVisible() = flicker.layerKeepVisible(textEditApp) + + @Presubmit + @Test + fun primaryAppBoundsKeepVisible() = + flicker.splitAppLayerBoundsKeepVisible( + primaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) + + @Presubmit + @Test + fun textEditAppBoundsKeepVisible() = + flicker.splitAppLayerBoundsKeepVisible( + textEditApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true + ) + + @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp) + + @Presubmit @Test fun textEditAppWindowKeepVisible() = flicker.appWindowKeepVisible(textEditApp) + + /** {@inheritDoc} */ + @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + ignoreLayers = + listOf( + ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT, + ComponentNameMatcher.IME_SNAPSHOT, + EdgeExtensionComponentMatcher(), + MagnifierLayer, + PopupWindowLayer + ) + ) + } + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt new file mode 100644 index 000000000000..d0f02e2bf514 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.appWindowBecomesInvisible +import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.layerBecomesInvisible +import com.android.wm.shell.flicker.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible +import com.android.wm.shell.flicker.splitScreenDismissed +import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test dismiss split screen by dragging the divider bar. + * + * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByDivider` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class DismissSplitScreenByDivider(flicker: FlickerTest) : SplitScreenBase(flicker) { + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } + transitions { + if (tapl.isTablet) { + SplitScreenUtils.dragDividerToDismissSplit( + device, + wmHelper, + dragToRight = false, + dragToBottom = true + ) + } else { + SplitScreenUtils.dragDividerToDismissSplit( + device, + wmHelper, + dragToRight = true, + dragToBottom = true + ) + } + wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify() + } + } + + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false) + + @Presubmit + @Test + fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible() + + @Presubmit + @Test + fun primaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(secondaryApp) + + @Presubmit + @Test + fun primaryAppBoundsBecomesInvisible() = + flicker.splitAppLayerBoundsBecomesInvisible( + primaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) + + @Presubmit + @Test + fun secondaryAppBoundsIsFullscreenAtEnd() { + flicker.assertLayers { + this.isVisible(secondaryApp) + .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + .then() + .isInvisible(secondaryApp) + .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + .then() + .isVisible(secondaryApp, isOptional = true) + .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT, isOptional = true) + .then() + .contains(SPLIT_SCREEN_DIVIDER_COMPONENT) + .then() + .invoke("secondaryAppBoundsIsFullscreenAtEnd") { + val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation) + it.visibleRegion(secondaryApp).coversExactly(displayBounds) + } + } + } + + @Presubmit + @Test + fun primaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp) + + /** {@inheritDoc} */ + @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 206753786) + @Test + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt new file mode 100644 index 000000000000..b44b681704ba --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.wm.shell.flicker.appWindowBecomesInvisible +import com.android.wm.shell.flicker.layerBecomesInvisible +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible +import com.android.wm.shell.flicker.splitScreenDismissed +import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test dismiss split screen by go home. + * + * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByGoHome` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class DismissSplitScreenByGoHome(flicker: FlickerTest) : SplitScreenBase(flicker) { + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } + transitions { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + } + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true) + + @Presubmit + @Test + fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible() + + @FlakyTest(bugId = 241525302) + @Test + fun primaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp) + + // TODO(b/245472831): Move back to presubmit after shell transitions landing. + @FlakyTest(bugId = 245472831) + @Test + fun secondaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp) + + // TODO(b/245472831): Move back to presubmit after shell transitions landing. + @FlakyTest(bugId = 245472831) + @Test + fun primaryAppBoundsBecomesInvisible() = + flicker.splitAppLayerBoundsBecomesInvisible( + primaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) + + @FlakyTest(bugId = 250530241) + @Test + fun secondaryAppBoundsBecomesInvisible() = + flicker.splitAppLayerBoundsBecomesInvisible( + secondaryApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true + ) + + @Presubmit + @Test + fun primaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(secondaryApp) + + /** {@inheritDoc} */ + @FlakyTest(bugId = 251268711) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 206753786) + @Test + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt new file mode 100644 index 000000000000..514365fbd71a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.appWindowIsVisibleAtStart +import com.android.wm.shell.flicker.appWindowKeepVisible +import com.android.wm.shell.flicker.layerKeepVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsChanges +import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test resize split by dragging the divider bar. + * + * To run this test: `atest WMShellFlickerTests:DragDividerToResize` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) { + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } + transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) } + } + + @Before + fun before() { + Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart) + } + + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() { + flicker.appWindowIsVisibleAtStart(primaryApp) + flicker.appWindowIsVisibleAtStart(secondaryApp) + flicker.splitScreenDividerIsVisibleAtStart() + + flicker.appWindowIsVisibleAtEnd(primaryApp) + flicker.appWindowIsVisibleAtEnd(secondaryApp) + flicker.splitScreenDividerIsVisibleAtEnd() + + // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is + // robust enough to get the correct end state. + } + + @Presubmit + @Test + fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + + @Presubmit + @Test + fun primaryAppLayerKeepVisible() { + Assume.assumeFalse(isShellTransitionsEnabled) + flicker.layerKeepVisible(primaryApp) + } + + @FlakyTest(bugId = 263213649) + @Test + fun primaryAppLayerKeepVisible_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + flicker.layerKeepVisible(primaryApp) + } + + @Presubmit + @Test + fun secondaryAppLayerVisibilityChanges() { + flicker.assertLayers { + this.isVisible(secondaryApp) + .then() + .isInvisible(secondaryApp) + .then() + .isVisible(secondaryApp) + } + } + + @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp) + + @Presubmit + @Test + fun primaryAppBoundsChanges() { + Assume.assumeFalse(isShellTransitionsEnabled) + flicker.splitAppLayerBoundsChanges( + primaryApp, + landscapePosLeft = true, + portraitPosTop = false + ) + } + + @FlakyTest(bugId = 263213649) + @Test + fun primaryAppBoundsChanges_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + flicker.splitAppLayerBoundsChanges( + primaryApp, + landscapePosLeft = true, + portraitPosTop = false + ) + } + + @Presubmit + @Test + fun secondaryAppBoundsChanges() = + flicker.splitAppLayerBoundsChanges( + secondaryApp, + landscapePosLeft = false, + portraitPosTop = true + ) + + /** {@inheritDoc} */ + @FlakyTest(bugId = 263213649) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt index 702710caded7..4e36c367f226 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt @@ -16,22 +16,25 @@ package com.android.wm.shell.flicker.splitscreen +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.view.WindowManagerPolicyConstants import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesVisible import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import com.android.wm.shell.flicker.splitScreenEntered import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -41,8 +44,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test enter split screen by dragging app icon from all apps. - * This test is only for large screen devices. + * Test enter split screen by dragging app icon from all apps. This test is only for large screen + * devices. * * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps` */ @@ -50,74 +53,141 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -class EnterSplitScreenByDragFromAllApps( - testSpec: FlickerTestParameter -) : SplitScreenBase(testSpec) { +class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase(flicker) { @Before - open fun before() { - Assume.assumeTrue(taplInstrumentation.isTablet) + fun before() { + Assume.assumeTrue(tapl.isTablet) } override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - eachRun { - taplInstrumentation.goHome() - primaryApp.launchViaIntent(wmHelper) - } + tapl.goHome() + primaryApp.launchViaIntent(wmHelper) } transitions { - taplInstrumentation.launchedAppState.taskbar + tapl.launchedAppState.taskbar .openAllApps() .getAppIcon(secondaryApp.appName) - .dragToSplitscreen(secondaryApp.component.packageName, - primaryApp.component.packageName) + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } + @IwTest(focusArea = "sysui") @Presubmit @Test - fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible() + fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false) @Presubmit @Test - fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component) + fun splitScreenDividerBecomesVisible() { + Assume.assumeFalse(isShellTransitionsEnabled) + flicker.splitScreenDividerBecomesVisible() + } + // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready. @Presubmit @Test - fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component) + fun splitScreenDividerIsVisibleAtEnd_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } + } + + @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp) @Presubmit @Test - fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - testSpec.endRotation, primaryApp.component, false /* splitLeftTop */) + fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp) @Presubmit @Test - fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible( - testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */) + fun primaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = false, + portraitPosTop = false + ) @Presubmit @Test - fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component) + fun secondaryAppBoundsBecomesVisible() = + flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp) @Presubmit @Test - fun secondaryAppWindowBecomesVisible() = - testSpec.appWindowBecomesVisible(secondaryApp.component) + fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp) + + /** {@inheritDoc} */ + @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. - supportedNavigationModes = - listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)) + supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt index 7323d992ecd4..5d37e858c15f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt @@ -16,23 +16,24 @@ package com.android.wm.shell.flicker.splitscreen +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.view.WindowManagerPolicyConstants import androidx.test.filters.RequiresDevice -import androidx.test.uiautomator.By -import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import com.android.wm.shell.flicker.splitScreenEntered import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -42,8 +43,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test enter split screen by dragging app icon from notification. - * This test is only for large screen devices. + * Test enter split screen by dragging app icon from notification. This test is only for large + * screen devices. * * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification` */ @@ -51,88 +52,163 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -class EnterSplitScreenByDragFromNotification( - testSpec: FlickerTestParameter -) : SplitScreenBase(testSpec) { +class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreenBase(flicker) { - private val sendNotificationApp = SplitScreenHelper.getSendNotification(instrumentation) + private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) @Before fun before() { - Assume.assumeTrue(taplInstrumentation.isTablet) + Assume.assumeTrue(tapl.isTablet) } + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - eachRun { - // Send a notification - sendNotificationApp.launchViaIntent(wmHelper) - val sendNotification = device.wait( - Until.findObject(By.text("Send Notification")), - SplitScreenHelper.TIMEOUT_MS - ) - sendNotification?.click() ?: error("Send notification button not found") - - taplInstrumentation.goHome() - primaryApp.launchViaIntent(wmHelper) - } + // Send a notification + sendNotificationApp.launchViaIntent(wmHelper) + sendNotificationApp.postNotification(wmHelper) + tapl.goHome() + primaryApp.launchViaIntent(wmHelper) } transitions { - SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper) - } - teardown { - eachRun { - sendNotificationApp.exit(wmHelper) - } + SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp) } + teardown { sendNotificationApp.exit(wmHelper) } } + @IwTest(focusArea = "sysui") @Presubmit @Test - fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible() + fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false) @Presubmit @Test - fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component) + fun splitScreenDividerBecomesVisible() { + Assume.assumeFalse(isShellTransitionsEnabled) + flicker.splitScreenDividerBecomesVisible() + } + // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready. @Presubmit @Test - fun secondaryAppLayerBecomesVisible() = - testSpec.layerBecomesVisible(sendNotificationApp.component) + fun splitScreenDividerIsVisibleAtEnd_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } + } + + @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp) @Presubmit @Test - fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - testSpec.endRotation, primaryApp.component, false /* splitLeftTop */ - ) + fun secondaryAppLayerBecomesVisible() { + Assume.assumeFalse(isShellTransitionsEnabled) + flicker.assertLayers { + this.isInvisible(sendNotificationApp) + .then() + .isVisible(sendNotificationApp) + .then() + .isInvisible(sendNotificationApp) + .then() + .isVisible(sendNotificationApp) + } + } + // TODO(b/245472831): Align to legacy transition after shell transition ready. @Presubmit @Test - fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible( - testSpec.endRotation, sendNotificationApp.component, true /* splitLeftTop */ - ) + fun secondaryAppLayerBecomesVisible_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + flicker.layerBecomesVisible(sendNotificationApp) + } @Presubmit @Test - fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component) + fun primaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = false, + portraitPosTop = false + ) @Presubmit @Test - fun secondaryAppWindowIsVisibleAtEnd() = - testSpec.appWindowIsVisibleAtEnd(sendNotificationApp.component) + fun secondaryAppBoundsBecomesVisible() = + flicker.splitAppLayerBoundsBecomesVisibleByDrag(sendNotificationApp) + + @Presubmit + @Test + fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(sendNotificationApp) + + /** {@inheritDoc} */ + @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. - supportedNavigationModes = - listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY) + supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt new file mode 100644 index 000000000000..d086f7e04486 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import com.android.wm.shell.flicker.splitScreenEntered +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test enter split screen by dragging a shortcut. This test is only for large screen devices. + * + * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class EnterSplitScreenByDragFromShortcut(flicker: FlickerTest) : SplitScreenBase(flicker) { + + @Before + fun before() { + Assume.assumeTrue(tapl.isTablet) + } + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) + } + transitions { + tapl.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .openDeepShortcutMenu() + .getMenuItem("Split Screen Secondary Activity") + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + } + + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() = + flicker.splitScreenEntered( + primaryApp, + secondaryApp, + fromOtherApp = false, + appExistAtStart = false + ) + + @Presubmit + @Test + fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible() + + @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp) + + @Presubmit + @Test + fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = false, + portraitPosTop = false + ) + + @Presubmit + @Test + fun secondaryAppBoundsBecomesVisible() = + flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp) + + @Presubmit + @Test + fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() { + flicker.assertWm { + this.notContains(secondaryApp) + .then() + .isAppWindowInvisible(secondaryApp, isOptional = true) + .then() + .isAppWindowVisible(secondaryApp) + } + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 241523824) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. + supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt index 05c6e24ee89d..795a2c4f43ba 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt @@ -16,22 +16,25 @@ package com.android.wm.shell.flicker.splitscreen +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.view.WindowManagerPolicyConstants import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesVisible import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import com.android.wm.shell.flicker.splitScreenEntered import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -41,8 +44,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test enter split screen by dragging app icon from taskbar. - * This test is only for large screen devices. + * Test enter split screen by dragging app icon from taskbar. This test is only for large screen + * devices. * * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar` */ @@ -50,79 +53,159 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -class EnterSplitScreenByDragFromTaskbar( - testSpec: FlickerTestParameter -) : SplitScreenBase(testSpec) { +class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(flicker) { @Before fun before() { - Assume.assumeTrue(taplInstrumentation.isTablet) + Assume.assumeTrue(tapl.isTablet) } + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - eachRun { - taplInstrumentation.goHome() - SplitScreenHelper.createShortcutOnHotseatIfNotExist( - taplInstrumentation, secondaryApp.appName - ) - primaryApp.launchViaIntent(wmHelper) - } + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) } transitions { - taplInstrumentation.launchedAppState.taskbar + tapl.launchedAppState.taskbar .getAppIcon(secondaryApp.appName) - .dragToSplitscreen( - secondaryApp.component.packageName, - primaryApp.component.packageName - ) + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } + @IwTest(focusArea = "sysui") @Presubmit @Test - fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible() + fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false) @Presubmit @Test - fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component) + fun splitScreenDividerBecomesVisible() { + Assume.assumeFalse(isShellTransitionsEnabled) + flicker.splitScreenDividerBecomesVisible() + } + // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready. @Presubmit @Test - fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component) + fun splitScreenDividerIsVisibleAtEnd_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } + } + + @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp) @Presubmit @Test - fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - testSpec.endRotation, primaryApp.component, false /* splitLeftTop */ - ) + fun secondaryAppLayerBecomesVisible() { + Assume.assumeFalse(isShellTransitionsEnabled) + flicker.assertLayers { + this.isInvisible(secondaryApp) + .then() + .isVisible(secondaryApp) + .then() + .isInvisible(secondaryApp) + .then() + .isVisible(secondaryApp) + } + } + + // TODO(b/245472831): Align to legacy transition after shell transition ready. + @Presubmit + @Test + fun secondaryAppLayerBecomesVisible_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + flicker.layerBecomesVisible(secondaryApp) + } @Presubmit @Test - fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible( - testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */ - ) + fun primaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = false, + portraitPosTop = false + ) @Presubmit @Test - fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component) + fun secondaryAppBoundsBecomesVisible() = + flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp) @Presubmit @Test - fun secondaryAppWindowBecomesVisible() = - testSpec.appWindowBecomesVisible(secondaryApp.component) + fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp) + + /** {@inheritDoc} */ + @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedNavigationModes = - listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt new file mode 100644 index 000000000000..a9cbb7419417 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2022 The Android 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.splitscreen + +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.wm.shell.flicker.appWindowBecomesVisible +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import com.android.wm.shell.flicker.splitScreenEntered +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test enter split screen from Overview. + * + * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromOverview` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class EnterSplitScreenFromOverview(flicker: FlickerTest) : SplitScreenBase(flicker) { + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withHomeActivityVisible() + .waitForAndVerify() + } + transitions { + SplitScreenUtils.splitFromOverview(tapl, device) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + } + + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) + + @Presubmit + @Test + fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible() + + @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp) + + @Presubmit + @Test + fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) + + @Presubmit + @Test + fun secondaryAppBoundsBecomesVisible() { + flicker.splitAppLayerBoundsBecomesVisible( + secondaryApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true + ) + } + + @Presubmit + @Test + fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp) + + /** {@inheritDoc} */ + @FlakyTest(bugId = 251269324) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 252736515) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt index 52c2daf96a3c..8c0a303189e1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt @@ -16,44 +16,29 @@ package com.android.wm.shell.flicker.splitscreen -import android.app.Instrumentation import android.content.Context -import androidx.test.platform.app.InstrumentationRegistry -import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest import com.android.server.wm.flicker.helpers.setRotation -import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import com.android.wm.shell.flicker.BaseTest -abstract class SplitScreenBase(protected val testSpec: FlickerTestParameter) { - protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - protected val taplInstrumentation = LauncherInstrumentation() +abstract class SplitScreenBase(flicker: FlickerTest) : BaseTest(flicker) { protected val context: Context = instrumentation.context - protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation) - protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation) + protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - transition(this) - } - } - - protected open val transition: FlickerBuilder.() -> Unit + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit get() = { setup { - test { - taplInstrumentation.setEnableRotation(true) - setRotation(testSpec.startRotation) - taplInstrumentation.setExpectedRotation(testSpec.startRotation) - } + tapl.setEnableRotation(true) + setRotation(flicker.scenario.startRotation) + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + tapl.workspace.switchToOverview().dismissAllTasks() } teardown { - eachRun { - primaryApp.exit(wmHelper) - secondaryApp.exit(wmHelper) - } + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt new file mode 100644 index 000000000000..f3927d405467 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.app.Instrumentation +import android.graphics.Point +import android.os.SystemClock +import android.view.InputDevice +import android.view.MotionEvent +import android.view.ViewConfiguration +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.StandardAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.IComponentNameMatcher +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME +import java.util.Collections +import org.junit.Assert.assertNotNull + +internal object SplitScreenUtils { + private const val TIMEOUT_MS = 3_000L + private const val DRAG_DURATION_MS = 1_000L + private const val NOTIFICATION_SCROLLER = "notification_stack_scroller" + private const val DIVIDER_BAR = "docked_divider_handle" + private const val OVERVIEW_SNAPSHOT = "snapshot" + private const val GESTURE_STEP_MS = 16L + private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L + private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") + + private val notificationScrollerSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER) + private val notificationContentSelector: BySelector + get() = By.text("Flicker Test Notification") + private val dividerBarSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR) + private val overviewSnapshotSelector: BySelector + get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT) + + fun getPrimary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) + + fun getSecondary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Secondary.LABEL, + ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent() + ) + + fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper = + NonResizeableAppHelper(instrumentation) + + fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper = + NotificationAppHelper(instrumentation) + + fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation) + + fun waitForSplitComplete( + wmHelper: WindowManagerStateHelper, + primaryApp: IComponentMatcher, + secondaryApp: IComponentMatcher, + ) { + wmHelper + .StateSyncBuilder() + .withWindowSurfaceAppeared(primaryApp) + .withWindowSurfaceAppeared(secondaryApp) + .withSplitDividerVisible() + .waitForAndVerify() + } + + fun enterSplit( + wmHelper: WindowManagerStateHelper, + tapl: LauncherInstrumentation, + device: UiDevice, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + splitFromOverview(tapl, device) + waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) { + // Note: The initial split position in landscape is different between tablet and phone. + // In landscape, tablet will let the first app split to right side, and phone will + // split to left side. + if (tapl.isTablet) { + // TAPL's currentTask on tablet is sometimes not what we expected if the overview + // contains more than 3 task views. We need to use uiautomator directly to find the + // second task to split. + tapl.workspace.switchToOverview().overviewActions.clickSplit() + val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS) + if (snapshots == null || snapshots.size < 1) { + error("Fail to find a overview snapshot to split.") + } + + // Find the second task in the upper right corner in split select mode by sorting + // 'left' in descending order and 'top' in ascending order. + Collections.sort( + snapshots, + { t1: UiObject2, t2: UiObject2 -> + t2.getVisibleBounds().left - t1.getVisibleBounds().left + } + ) + Collections.sort( + snapshots, + { t1: UiObject2, t2: UiObject2 -> + t1.getVisibleBounds().top - t2.getVisibleBounds().top + } + ) + snapshots[0].click() + } else { + tapl.workspace + .switchToOverview() + .currentTask + .tapMenu() + .tapSplitMenuItem() + .currentTask + .open() + } + SystemClock.sleep(TIMEOUT_MS) + } + + fun dragFromNotificationToSplit( + instrumentation: Instrumentation, + device: UiDevice, + wmHelper: WindowManagerStateHelper + ) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + + // Pull down the notifications + device.swipe( + displayBounds.centerX(), + 5, + displayBounds.centerX(), + displayBounds.bottom, + 50 /* steps */ + ) + SystemClock.sleep(TIMEOUT_MS) + + // Find the target notification + val notificationScroller = + device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS) + ?: error("Unable to find view $notificationScrollerSelector") + var notificationContent = notificationScroller.findObject(notificationContentSelector) + + while (notificationContent == null) { + device.swipe( + displayBounds.centerX(), + displayBounds.centerY(), + displayBounds.centerX(), + displayBounds.centerY() - 150, + 20 /* steps */ + ) + notificationContent = notificationScroller.findObject(notificationContentSelector) + } + + // Drag to split + val dragStart = notificationContent.visibleCenter + val dragMiddle = Point(dragStart.x + 50, dragStart.y) + val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4) + val downTime = SystemClock.uptimeMillis() + + touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart) + // It needs a horizontal movement to trigger the drag + touchMove( + instrumentation, + downTime, + SystemClock.uptimeMillis(), + DRAG_DURATION_MS, + dragStart, + dragMiddle + ) + touchMove( + instrumentation, + downTime, + SystemClock.uptimeMillis(), + DRAG_DURATION_MS, + dragMiddle, + dragEnd + ) + // Wait for a while to start splitting + SystemClock.sleep(TIMEOUT_MS) + touch( + instrumentation, + MotionEvent.ACTION_UP, + downTime, + SystemClock.uptimeMillis(), + GESTURE_STEP_MS, + dragEnd + ) + SystemClock.sleep(TIMEOUT_MS) + } + + fun touch( + instrumentation: Instrumentation, + action: Int, + downTime: Long, + eventTime: Long, + duration: Long, + point: Point + ) { + val motionEvent = + MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0) + motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionEvent, true) + motionEvent.recycle() + SystemClock.sleep(duration) + } + + fun touchMove( + instrumentation: Instrumentation, + downTime: Long, + eventTime: Long, + duration: Long, + from: Point, + to: Point + ) { + val steps: Long = duration / GESTURE_STEP_MS + var currentTime = eventTime + var currentX = from.x.toFloat() + var currentY = from.y.toFloat() + val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat() + val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat() + + for (i in 1..steps) { + val motionMove = + MotionEvent.obtain( + downTime, + currentTime, + MotionEvent.ACTION_MOVE, + currentX, + currentY, + 0 + ) + motionMove.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionMove, true) + motionMove.recycle() + + currentTime += GESTURE_STEP_MS + if (i == steps - 1) { + currentX = to.x.toFloat() + currentY = to.y.toFloat() + } else { + currentX += stepX + currentY += stepY + } + SystemClock.sleep(GESTURE_STEP_MS) + } + } + + fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) { + tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0)) + val allApps = tapl.workspace.switchToAllApps() + allApps.freeze() + try { + allApps.getAppIcon(appName).dragToHotseat(0) + } finally { + allApps.unfreeze() + } + } + + fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3)) + + wmHelper + .StateSyncBuilder() + .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER) + .waitForAndVerify() + } + + fun dragDividerToDismissSplit( + device: UiDevice, + wmHelper: WindowManagerStateHelper, + dragToRight: Boolean, + dragToBottom: Boolean + ) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag( + Point( + if (dragToRight) { + displayBounds.width * 4 / 5 + } else { + displayBounds.width * 1 / 5 + }, + if (dragToBottom) { + displayBounds.height * 4 / 5 + } else { + displayBounds.height * 1 / 5 + } + ) + ) + } + + fun doubleTapDividerToSwitch(device: UiDevice) { + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + val interval = + (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2 + dividerBar.click() + SystemClock.sleep(interval.toLong()) + dividerBar.click() + } + + fun copyContentInSplit( + instrumentation: Instrumentation, + device: UiDevice, + sourceApp: IComponentNameMatcher, + destinationApp: IComponentNameMatcher, + ) { + // Copy text from sourceApp + val textView = + device.wait( + Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")), + TIMEOUT_MS + ) + assertNotNull("Unable to find the TextView", textView) + textView.click(LONG_PRESS_TIME_MS) + + val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS) + assertNotNull("Unable to find the copy button", copyBtn) + copyBtn.click() + + // Paste text to destinationApp + val editText = + device.wait( + Until.findObject(By.res(destinationApp.packageName, "plain_text_input")), + TIMEOUT_MS + ) + assertNotNull("Unable to find the EditText", editText) + editText.click(LONG_PRESS_TIME_MS) + + val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS) + assertNotNull("Unable to find the paste button", pasteBtn) + pasteBtn.click() + + // Verify text + if (!textView.text.contentEquals(editText.text)) { + error("Fail to copy content in split") + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt new file mode 100644 index 000000000000..c7b81d924a9b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.appWindowIsVisibleAtStart +import com.android.wm.shell.flicker.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.layerKeepVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test double tap the divider bar to switch the two apps. + * + * To run this test: `atest WMShellFlickerTests:SwitchAppByDoubleTapDivider` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SwitchAppByDoubleTapDivider(flicker: FlickerTest) : SplitScreenBase(flicker) { + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } + transitions { + SplitScreenUtils.doubleTapDividerToSwitch(device) + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + + waitForLayersToSwitch(wmHelper) + waitForWindowsToSwitch(wmHelper) + } + } + + private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) { + wmHelper + .StateSyncBuilder() + .add("appWindowsSwitched") { + val primaryAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + primaryApp.windowMatchesAnyOf(window) + } + ?: return@add false + val secondaryAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + secondaryApp.windowMatchesAnyOf(window) + } + ?: return@add false + + if (isLandscape(flicker.scenario.endRotation)) { + return@add if (flicker.scenario.isTablet) { + secondaryAppWindow.frame.right <= primaryAppWindow.frame.left + } else { + primaryAppWindow.frame.right <= secondaryAppWindow.frame.left + } + } else { + return@add if (flicker.scenario.isTablet) { + primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top + } else { + primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top + } + } + } + .waitForAndVerify() + } + + private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) { + wmHelper + .StateSyncBuilder() + .add("appLayersSwitched") { + val primaryAppLayer = + it.layerState.visibleLayers.firstOrNull { window -> + primaryApp.layerMatchesAnyOf(window) + } + ?: return@add false + val secondaryAppLayer = + it.layerState.visibleLayers.firstOrNull { window -> + secondaryApp.layerMatchesAnyOf(window) + } + ?: return@add false + + val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false + val secondaryVisibleRegion = + secondaryAppLayer.visibleRegion?.bounds ?: return@add false + + if (isLandscape(flicker.scenario.endRotation)) { + return@add if (flicker.scenario.isTablet) { + secondaryVisibleRegion.right <= primaryVisibleRegion.left + } else { + primaryVisibleRegion.right <= secondaryVisibleRegion.left + } + } else { + return@add if (flicker.scenario.isTablet) { + primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } else { + primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } + } + } + .waitForAndVerify() + } + + private fun isLandscape(rotation: PlatformConsts.Rotation): Boolean { + val displayBounds = WindowUtils.getDisplayBounds(rotation) + return displayBounds.width > displayBounds.height + } + + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() { + flicker.appWindowIsVisibleAtStart(primaryApp) + flicker.appWindowIsVisibleAtStart(secondaryApp) + flicker.splitScreenDividerIsVisibleAtStart() + + flicker.appWindowIsVisibleAtEnd(primaryApp) + flicker.appWindowIsVisibleAtEnd(secondaryApp) + flicker.splitScreenDividerIsVisibleAtEnd() + + // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is + // robust enough to get the correct end state. + } + + @Presubmit + @Test + fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + + @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp) + + @Presubmit + @Test + fun secondaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(secondaryApp) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true + ) + + @Presubmit + @Test + fun secondaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + secondaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) + + @Presubmit + @Test + fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp) + + /** {@inheritDoc} */ + @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. + supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt new file mode 100644 index 000000000000..940e0e93d524 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.appWindowBecomesVisible +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import com.android.wm.shell.flicker.splitScreenEntered +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switch to split pair from another app. + * + * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromAnotherApp` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SwitchBackToSplitFromAnotherApp(flicker: FlickerTest) : SplitScreenBase(flicker) { + val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + thirdApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify() + } + transitions { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + } + + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) + + @Presubmit + @Test + fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible() + + @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) + + @Presubmit + @Test + fun secondaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + secondaryApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true + ) + + @Presubmit + @Test + fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp) + + /** {@inheritDoc} */ + @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. + supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt new file mode 100644 index 000000000000..85812c420f3b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.appWindowBecomesVisible +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import com.android.wm.shell.flicker.splitScreenEntered +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switch to split pair from home. + * + * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker) { + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + transitions { + tapl.workspace.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + } + + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) + + @Presubmit + @Test + fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible() + + @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) + + @Presubmit + @Test + fun secondaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + secondaryApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true + ) + + @Presubmit + @Test + fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp) + + /** {@inheritDoc} */ + @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 252736515) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. + supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt new file mode 100644 index 000000000000..7c62433d8905 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.server.wm.traces.common.service.PlatformConsts +import com.android.wm.shell.flicker.appWindowBecomesVisible +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import com.android.wm.shell.flicker.splitScreenEntered +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test switch back to split pair from recent. + * + * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicker) { + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + transitions { + tapl.workspace.switchToOverview().currentTask.open() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + } + + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) + + @Presubmit + @Test + fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible() + + @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) + + @Presubmit + @Test + fun secondaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + secondaryApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true + ) + + @Presubmit + @Test + fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp) + + /** {@inheritDoc} */ + @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. + supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt new file mode 100644 index 000000000000..193ab98cf191 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2022 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.splitscreen + +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerBuilder +import com.android.server.wm.flicker.FlickerTest +import com.android.server.wm.flicker.FlickerTestFactory +import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.appWindowBecomesInvisible +import com.android.wm.shell.flicker.appWindowBecomesVisible +import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd +import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.appWindowIsVisibleAtStart +import com.android.wm.shell.flicker.layerBecomesInvisible +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider +import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switch between two split pairs. + * + * To run this test: `atest WMShellFlickerTests:SwitchBetweenSplitPairs` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SwitchBetweenSplitPairs(flicker: FlickerTest) : SplitScreenBase(flicker) { + private val thirdApp = SplitScreenUtils.getIme(instrumentation) + private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp) + } + transitions { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + teardown { + thirdApp.exit(wmHelper) + fourthApp.exit(wmHelper) + } + } + + @IwTest(focusArea = "sysui") + @Presubmit + @Test + fun cujCompleted() { + flicker.appWindowIsVisibleAtStart(thirdApp) + flicker.appWindowIsVisibleAtStart(fourthApp) + flicker.splitScreenDividerIsVisibleAtStart() + + flicker.appWindowIsVisibleAtEnd(primaryApp) + flicker.appWindowIsVisibleAtEnd(secondaryApp) + flicker.appWindowIsInvisibleAtEnd(thirdApp) + flicker.appWindowIsInvisibleAtEnd(fourthApp) + flicker.splitScreenDividerIsVisibleAtEnd() + } + + @Presubmit + @Test + fun splitScreenDividerInvisibleAtMiddle() = + flicker.assertLayers { + this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + .then() + .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + .then() + .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + } + + @FlakyTest(bugId = 247095572) + @Test + fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp) + + @FlakyTest(bugId = 247095572) + @Test + fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp) + + @FlakyTest(bugId = 247095572) + @Test + fun thirdAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(thirdApp) + + @FlakyTest(bugId = 247095572) + @Test + fun fourthAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(fourthApp) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) + + @Presubmit + @Test + fun secondaryAppBoundsIsVisibleAtEnd() = + flicker.splitAppLayerBoundsIsVisibleAtEnd( + secondaryApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true + ) + + @Presubmit + @Test + fun thirdAppBoundsIsVisibleAtBegin() = + flicker.assertLayersStart { + this.splitAppLayerBoundsSnapToDivider( + thirdApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false, + flicker.scenario.startRotation + ) + } + + @Presubmit + @Test + fun fourthAppBoundsIsVisibleAtBegin() = + flicker.assertLayersStart { + this.splitAppLayerBoundsSnapToDivider( + fourthApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true, + flicker.scenario.startRotation + ) + } + + @Presubmit + @Test + fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp) + + @Presubmit + @Test + fun thirdAppWindowBecomesVisible() = flicker.appWindowBecomesInvisible(thirdApp) + + @Presubmit + @Test + fun fourthAppWindowBecomesVisible() = flicker.appWindowBecomesInvisible(fourthApp) + + /** {@inheritDoc} */ + @FlakyTest(bugId = 251268711) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 206753786) + @Test + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp deleted file mode 100644 index ea606df1536d..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2020 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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test { - name: "WMShellFlickerTestApp", - srcs: ["**/*.java"], - sdk_version: "current", - test_suites: ["device-tests"], -} - -java_library { - name: "wmshell-flicker-test-components", - srcs: ["src/**/Components.java"], - sdk_version: "test_current", -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml deleted file mode 100644 index bc0b0b6292b4..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml +++ /dev/null @@ -1,147 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.wm.shell.flicker.testapp"> - - <uses-sdk android:minSdkVersion="29" - android:targetSdkVersion="29"/> - <application android:allowBackup="false" - android:supportsRtl="true"> - <activity android:name=".FixedActivity" - android:resizeableActivity="true" - android:supportsPictureInPicture="true" - android:launchMode="singleTop" - android:theme="@style/CutoutShortEdges" - android:label="FixedApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - <activity android:name=".PipActivity" - android:resizeableActivity="true" - android:supportsPictureInPicture="true" - android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" - android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity" - android:theme="@style/CutoutShortEdges" - android:launchMode="singleTop" - android:label="PipApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".ImeActivity" - android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity" - android:theme="@style/CutoutShortEdges" - android:label="ImeApp" - android:launchMode="singleTop" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".SplitScreenActivity" - android:resizeableActivity="true" - android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenActivity" - android:theme="@style/CutoutShortEdges" - android:label="SplitScreenPrimaryApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".SplitScreenSecondaryActivity" - android:resizeableActivity="true" - android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenSecondaryActivity" - android:theme="@style/CutoutShortEdges" - android:label="SplitScreenSecondaryApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".SendNotificationActivity" - android:taskAffinity="com.android.wm.shell.flicker.testapp.SendNotificationActivity" - android:theme="@style/CutoutShortEdges" - android:label="SendNotificationApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".NonResizeableActivity" - android:resizeableActivity="false" - android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity" - android:theme="@style/CutoutShortEdges" - android:label="NonResizeableApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".SimpleActivity" - android:taskAffinity="com.android.wm.shell.flicker.testapp.SimpleActivity" - android:theme="@style/CutoutShortEdges" - android:label="SimpleApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - <activity - android:name=".LaunchBubbleActivity" - android:label="LaunchBubbleApp" - android:exported="true" - android:theme="@style/CutoutShortEdges" - android:launchMode="singleTop"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <action android:name="android.intent.action.VIEW" /> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - <activity - android:name=".BubbleActivity" - android:label="BubbleApp" - android:exported="false" - android:theme="@style/CutoutShortEdges" - android:resizeableActivity="true" /> - </application> -</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png Binary files differdeleted file mode 100644 index d424a17b4157..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png +++ /dev/null diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml deleted file mode 100644 index b43f31da748d..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2021 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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="#FF000000" - android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/> - <path - android:fillColor="#FF000000" - android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/> - <path - android:fillColor="#FF000000" - android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/> -</vector> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml deleted file mode 100644 index 0e8c7a0fe64a..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2021 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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:pathData="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z" - android:fillColor="#000000" - android:fillType="evenOdd"/> -</vector> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml deleted file mode 100644 index f8b0ca3da26e..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2021 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <Button - android:id="@+id/button_finish" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:layout_marginStart="8dp" - android:text="Finish" /> - <Button - android:id="@+id/button_new_task" - android:layout_width="wrap_content" - android:layout_height="46dp" - android:layout_marginStart="8dp" - android:text="New Task" /> - <Button - android:id="@+id/button_new_bubble" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginEnd="8dp" - android:text="New Bubble" /> - - <Button - android:id="@+id/button_activity_for_result" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="8dp" - android:layout_marginStart="8dp" - android:text="Activity For Result" /> -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml deleted file mode 100644 index 4708cfd48381..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2018 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:focusableInTouchMode="true" - android:background="@android:color/holo_green_light"> - <EditText android:id="@+id/plain_text_input" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:inputType="text"/> -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml deleted file mode 100644 index f23c46455c63..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2021 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. ---> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:background="@android:color/black"> - - <Button - android:id="@+id/button_create" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:layout_centerVertical="true" - android:text="Add Bubble" /> - - <Button - android:id="@+id/button_cancel" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@id/button_create" - android:layout_centerHorizontal="true" - android:layout_marginTop="20dp" - android:text="Cancel Bubble" /> - - <Button - android:id="@+id/button_cancel_all" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@id/button_cancel" - android:layout_centerHorizontal="true" - android:layout_marginTop="20dp" - android:text="Cancel All Bubble" /> -</RelativeLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml deleted file mode 100644 index 45d5917f86d6..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2020 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:background="@android:color/holo_orange_light"> - - <TextView - android:id="@+id/NonResizeableTest" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:gravity="center_vertical|center_horizontal" - android:text="NonResizeableActivity" - android:textAppearance="?android:attr/textAppearanceLarge"/> - -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml deleted file mode 100644 index 8d59b567e59b..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2021 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. ---> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:background="@android:color/black"> - - <Button - android:id="@+id/button_send_notification" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:layout_centerVertical="true" - android:text="Send Notification" /> -</RelativeLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml deleted file mode 100644 index 229098313afa..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml +++ /dev/null @@ -1,141 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2020 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:background="@android:color/holo_blue_bright"> - - <!-- All the buttons (and other clickable elements) should be arranged in a way so that it is - possible to "cycle" over all them by clicking on the D-Pad DOWN button. The way we do it - here is by arranging them this vertical LL and by relying on the nextFocusDown attribute - where things are arranged differently and to circle back up to the top once we reach the - bottom. --> - - <Button - android:id="@+id/enter_pip" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Enter PIP" - android:onClick="enterPip"/> - - <CheckBox - android:id="@+id/with_custom_actions" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="With custom actions"/> - - <RadioGroup - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:checkedButton="@id/enter_pip_on_leave_disabled"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Enter PiP on home press"/> - - <RadioButton - android:id="@+id/enter_pip_on_leave_disabled" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Disabled" - android:onClick="onAutoPipSelected"/> - - <RadioButton - android:id="@+id/enter_pip_on_leave_manual" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Via code behind" - android:onClick="onAutoPipSelected"/> - - <RadioButton - android:id="@+id/enter_pip_on_leave_autoenter" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Auto-enter PiP" - android:onClick="onAutoPipSelected"/> - </RadioGroup> - - <RadioGroup - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:checkedButton="@id/ratio_default"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Ratio"/> - - <RadioButton - android:id="@+id/ratio_default" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Default" - android:onClick="onRatioSelected"/> - - <RadioButton - android:id="@+id/ratio_square" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Square [1:1]" - android:onClick="onRatioSelected"/> - - <RadioButton - android:id="@+id/ratio_wide" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Wide [2:1]" - android:onClick="onRatioSelected"/> - - <RadioButton - android:id="@+id/ratio_tall" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Tall [1:2]" - android:onClick="onRatioSelected"/> - </RadioGroup> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Media Session"/> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content"> - - <Button - android:id="@+id/media_session_start" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:nextFocusDown="@id/media_session_stop" - android:text="Start"/> - - <Button - android:id="@+id/media_session_stop" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:nextFocusDown="@id/enter_pip" - android:text="Stop"/> - - </LinearLayout> - -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml deleted file mode 100644 index 5d94e5177dcc..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2018 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/holo_orange_light"> - -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml deleted file mode 100644 index 84789f5a6c02..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2020 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:background="@android:color/holo_green_light"> - - <TextView - android:id="@+id/SplitScreenTest" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:gravity="center_vertical|center_horizontal" - android:text="PrimaryActivity" - android:textAppearance="?android:attr/textAppearanceLarge"/> - -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml deleted file mode 100644 index 674bb70ad01e..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2020 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:background="@android:color/holo_blue_light"> - - <TextView - android:id="@+id/SplitScreenTest" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:gravity="center_vertical|center_horizontal" - android:text="SecondaryActivity" - android:textAppearance="?android:attr/textAppearanceLarge"/> - -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml deleted file mode 100644 index 23b51cc06f04..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 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. - --> - -<resources> - <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault"> - <item name="android:windowBackground">@android:color/darker_gray</item> - </style> - - <style name="CutoutDefault" parent="@style/DefaultTheme"> - <item name="android:windowLayoutInDisplayCutoutMode">default</item> - </style> - - <style name="CutoutShortEdges" parent="@style/DefaultTheme"> - <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> - </style> - - <style name="CutoutNever" parent="@style/DefaultTheme"> - <item name="android:windowLayoutInDisplayCutoutMode">never</item> - </style> -</resources>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java deleted file mode 100644 index bc3bc75ab903..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2020 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.testapp; - - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.widget.Toast; - -public class BubbleActivity extends Activity { - private int mNotifId = 0; - - public BubbleActivity() { - super(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Intent intent = getIntent(); - if (intent != null) { - mNotifId = intent.getIntExtra(BubbleHelper.EXTRA_BUBBLE_NOTIF_ID, -1); - } else { - mNotifId = -1; - } - - setContentView(R.layout.activity_bubble); - } - - @Override - protected void onStart() { - super.onStart(); - } - - @Override - protected void onResume() { - super.onResume(); - } - - @Override - protected void onPause() { - super.onPause(); - } - - @Override - protected void onStop() { - super.onStop(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - String result = resultCode == Activity.RESULT_OK ? "OK" : "CANCELLED"; - Toast.makeText(this, "Activity result: " + result, Toast.LENGTH_SHORT).show(); - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java deleted file mode 100644 index 6cd93eff2803..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2021 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.testapp; - - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Person; -import android.content.Context; -import android.content.Intent; -import android.graphics.Point; -import android.graphics.drawable.Icon; -import android.os.SystemClock; -import android.service.notification.StatusBarNotification; -import android.view.WindowManager; - -import java.util.HashMap; - -public class BubbleHelper { - - static final String EXTRA_BUBBLE_NOTIF_ID = "EXTRA_BUBBLE_NOTIF_ID"; - static final String CHANNEL_ID = "bubbles"; - static final String CHANNEL_NAME = "Bubbles"; - static final int DEFAULT_HEIGHT_DP = 300; - - private static BubbleHelper sInstance; - - private final Context mContext; - private NotificationManager mNotificationManager; - private float mDisplayHeight; - - private HashMap<Integer, BubbleInfo> mBubbleMap = new HashMap<>(); - - private int mNextNotifyId = 0; - private int mColourIndex = 0; - - public static class BubbleInfo { - public int id; - public int height; - public Icon icon; - - public BubbleInfo(int id, int height, Icon icon) { - this.id = id; - this.height = height; - this.icon = icon; - } - } - - public static BubbleHelper getInstance(Context context) { - if (sInstance == null) { - sInstance = new BubbleHelper(context); - } - return sInstance; - } - - private BubbleHelper(Context context) { - mContext = context; - mNotificationManager = context.getSystemService(NotificationManager.class); - - NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, - NotificationManager.IMPORTANCE_DEFAULT); - channel.setDescription("Channel that posts bubbles"); - channel.setAllowBubbles(true); - mNotificationManager.createNotificationChannel(channel); - - Point p = new Point(); - WindowManager wm = context.getSystemService(WindowManager.class); - wm.getDefaultDisplay().getRealSize(p); - mDisplayHeight = p.y; - - } - - private int getNextNotifyId() { - int id = mNextNotifyId; - mNextNotifyId++; - return id; - } - - private Icon getIcon() { - return Icon.createWithResource(mContext, R.drawable.bg); - } - - public int addNewBubble(boolean autoExpand, boolean suppressNotif) { - int id = getNextNotifyId(); - BubbleInfo info = new BubbleInfo(id, DEFAULT_HEIGHT_DP, getIcon()); - mBubbleMap.put(info.id, info); - - Notification.BubbleMetadata data = getBubbleBuilder(info) - .setSuppressNotification(suppressNotif) - .setAutoExpandBubble(false) - .build(); - Notification notification = getNotificationBuilder(info.id) - .setBubbleMetadata(data).build(); - - mNotificationManager.notify(info.id, notification); - return info.id; - } - - private Notification.Builder getNotificationBuilder(int id) { - Person chatBot = new Person.Builder() - .setBot(true) - .setName("BubbleChat") - .setImportant(true) - .build(); - String shortcutId = "BubbleChat"; - return new Notification.Builder(mContext, CHANNEL_ID) - .setChannelId(CHANNEL_ID) - .setShortcutId(shortcutId) - .setContentTitle("BubbleChat") - .setContentIntent(PendingIntent.getActivity(mContext, 0, - new Intent(mContext, LaunchBubbleActivity.class), - PendingIntent.FLAG_UPDATE_CURRENT)) - .setStyle(new Notification.MessagingStyle(chatBot) - .setConversationTitle("BubbleChat") - .addMessage("BubbleChat", - SystemClock.currentThreadTimeMillis() - 300000, chatBot) - .addMessage("Is it me, " + id + ", you're looking for?", - SystemClock.currentThreadTimeMillis(), chatBot) - ) - .setSmallIcon(R.drawable.ic_bubble); - } - - private Notification.BubbleMetadata.Builder getBubbleBuilder(BubbleInfo info) { - Intent target = new Intent(mContext, BubbleActivity.class); - target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id); - PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target, - PendingIntent.FLAG_UPDATE_CURRENT); - - return new Notification.BubbleMetadata.Builder() - .setIntent(bubbleIntent) - .setIcon(info.icon) - .setDesiredHeight(info.height); - } - - public void cancel(int id) { - mNotificationManager.cancel(id); - } - - public void cancelAll() { - mNotificationManager.cancelAll(); - } - - public void cancelLast() { - StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications(); - if (activeNotifications.length > 0) { - mNotificationManager.cancel( - activeNotifications[activeNotifications.length - 1].getId()); - } - } - - public void cancelFirst() { - StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications(); - if (activeNotifications.length > 0) { - mNotificationManager.cancel(activeNotifications[0].getId()); - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java deleted file mode 100644 index a2b580da5898..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2020 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.testapp; - -import android.content.ComponentName; - -public class Components { - public static final String PACKAGE_NAME = "com.android.wm.shell.flicker.testapp"; - - public static class SimpleActivity { - public static final String LABEL = "SimpleApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".SimpleActivity"); - } - - public static class FixedActivity { - public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation"; - public static final String LABEL = "FixedApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".FixedActivity"); - } - - public static class NonResizeableActivity { - public static final String LABEL = "NonResizeableApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".NonResizeableActivity"); - } - - public static class PipActivity { - // Test App > Pip Activity - public static final String LABEL = "PipApp"; - public static final String MENU_ACTION_NO_OP = "No-Op"; - public static final String MENU_ACTION_ON = "On"; - public static final String MENU_ACTION_OFF = "Off"; - public static final String MENU_ACTION_CLEAR = "Clear"; - - // Intent action that this activity dynamically registers to enter picture-in-picture - public static final String ACTION_ENTER_PIP = PACKAGE_NAME + ".PipActivity.ENTER_PIP"; - // Intent action that this activity dynamically registers to set requested orientation. - // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra. - public static final String ACTION_SET_REQUESTED_ORIENTATION = - PACKAGE_NAME + ".PipActivity.SET_REQUESTED_ORIENTATION"; - - // Calls enterPictureInPicture() on creation - public static final String EXTRA_ENTER_PIP = "enter_pip"; - // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation} - public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation"; - // Adds a click listener to finish this activity when it is clicked - public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish"; - - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".PipActivity"); - } - - public static class ImeActivity { - public static final String LABEL = "ImeApp"; - public static final String ACTION_CLOSE_IME = - PACKAGE_NAME + ".action.CLOSE_IME"; - public static final String ACTION_OPEN_IME = - PACKAGE_NAME + ".action.OPEN_IME"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".ImeActivity"); - } - - public static class SplitScreenActivity { - public static final String LABEL = "SplitScreenPrimaryApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".SplitScreenActivity"); - } - - public static class SplitScreenSecondaryActivity { - public static final String LABEL = "SplitScreenSecondaryApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".SplitScreenSecondaryActivity"); - } - - public static class SendNotificationActivity { - public static final String LABEL = "SendNotificationApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".SendNotificationActivity"); - } - - public static class LaunchBubbleActivity { - public static final String LABEL = "LaunchBubbleApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".LaunchBubbleActivity"); - } - - public static class BubbleActivity { - public static final String LABEL = "BubbleApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".BubbleActivity"); - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java deleted file mode 100644 index d4ae6c1313bf..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2020 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.testapp; - -import static com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION; - -import android.os.Bundle; - -public class FixedActivity extends SimpleActivity { - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - // Set the fixed orientation if requested - if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) { - final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION)); - setRequestedOrientation(ori); - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java deleted file mode 100644 index 59c64a1345ab..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2018 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.testapp; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; - -public class ImeActivity extends Activity { - private static final String ACTION_OPEN_IME = - "com.android.wm.shell.flicker.testapp.action.OPEN_IME"; - private static final String ACTION_CLOSE_IME = - "com.android.wm.shell.flicker.testapp.action.CLOSE_IME"; - - private InputMethodManager mImm; - private View mEditText; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); - setContentView(R.layout.activity_ime); - - mEditText = findViewById(R.id.plain_text_input); - mImm = getSystemService(InputMethodManager.class); - - handleIntent(getIntent()); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - final String action = intent.getAction(); - if (ACTION_OPEN_IME.equals(action)) { - mEditText.requestFocus(); - mImm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED); - } else if (ACTION_CLOSE_IME.equals(action)) { - mImm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); - mEditText.clearFocus(); - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java deleted file mode 100644 index 71fa66d8a61c..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2021 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.testapp; - - -import android.app.Activity; -import android.app.Person; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager; -import android.graphics.drawable.Icon; -import android.os.Bundle; -import android.view.View; - -import java.util.Arrays; - -public class LaunchBubbleActivity extends Activity { - - private BubbleHelper mBubbleHelper; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addInboxShortcut(getApplicationContext()); - mBubbleHelper = BubbleHelper.getInstance(this); - setContentView(R.layout.activity_main); - findViewById(R.id.button_create).setOnClickListener(this::add); - findViewById(R.id.button_cancel).setOnClickListener(this::cancel); - findViewById(R.id.button_cancel_all).setOnClickListener(this::cancelAll); - } - - private void add(View v) { - mBubbleHelper.addNewBubble(false /* autoExpand */, false /* suppressNotif */); - } - - private void cancel(View v) { - mBubbleHelper.cancelLast(); - } - - private void cancelAll(View v) { - mBubbleHelper.cancelAll(); - } - - private void addInboxShortcut(Context context) { - Icon icon = Icon.createWithResource(this, R.drawable.bg); - Person[] persons = new Person[4]; - for (int i = 0; i < persons.length; i++) { - persons[i] = new Person.Builder() - .setBot(false) - .setIcon(icon) - .setName("google" + i) - .setImportant(true) - .build(); - } - - ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "BubbleChat") - .setShortLabel("BubbleChat") - .setLongLived(true) - .setIntent(new Intent(Intent.ACTION_VIEW)) - .setIcon(Icon.createWithResource(context, R.drawable.ic_message)) - .setPersons(persons) - .build(); - ShortcutManager scmanager = context.getSystemService(ShortcutManager.class); - scmanager.addDynamicShortcuts(Arrays.asList(shortcut)); - } - -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java deleted file mode 100644 index 24275e002c7f..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2020 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.testapp; - -import android.app.Activity; -import android.os.Bundle; - -public class NonResizeableActivity extends Activity { - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - setContentView(R.layout.activity_non_resizeable); - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java deleted file mode 100644 index 615b1730579c..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright (C) 2020 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.testapp; - -import static android.media.MediaMetadata.METADATA_KEY_TITLE; -import static android.media.session.PlaybackState.ACTION_PAUSE; -import static android.media.session.PlaybackState.ACTION_PLAY; -import static android.media.session.PlaybackState.ACTION_STOP; -import static android.media.session.PlaybackState.STATE_PAUSED; -import static android.media.session.PlaybackState.STATE_PLAYING; -import static android.media.session.PlaybackState.STATE_STOPPED; - -import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP; -import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION; -import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP; -import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION; - -import android.app.Activity; -import android.app.PendingIntent; -import android.app.PictureInPictureParams; -import android.app.RemoteAction; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.drawable.Icon; -import android.media.MediaMetadata; -import android.media.session.MediaSession; -import android.media.session.PlaybackState; -import android.os.Bundle; -import android.util.Log; -import android.util.Rational; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.widget.CheckBox; -import android.widget.RadioButton; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class PipActivity extends Activity { - private static final String TAG = PipActivity.class.getSimpleName(); - /** - * A media session title for when the session is in {@link STATE_PLAYING}. - * TvPipNotificationTests check whether the actual notification title matches this string. - */ - private static final String TITLE_STATE_PLAYING = "TestApp media is playing"; - /** - * A media session title for when the session is in {@link STATE_PAUSED}. - * TvPipNotificationTests check whether the actual notification title matches this string. - */ - private static final String TITLE_STATE_PAUSED = "TestApp media is paused"; - - private static final Rational RATIO_DEFAULT = null; - private static final Rational RATIO_SQUARE = new Rational(1, 1); - private static final Rational RATIO_WIDE = new Rational(2, 1); - private static final Rational RATIO_TALL = new Rational(1, 2); - - private static final String PIP_ACTION_NO_OP = "No-Op"; - private static final String PIP_ACTION_OFF = "Off"; - private static final String PIP_ACTION_ON = "On"; - private static final String PIP_ACTION_CLEAR = "Clear"; - private static final String ACTION_NO_OP = "com.android.wm.shell.flicker.testapp.NO_OP"; - private static final String ACTION_SWITCH_OFF = - "com.android.wm.shell.flicker.testapp.SWITCH_OFF"; - private static final String ACTION_SWITCH_ON = "com.android.wm.shell.flicker.testapp.SWITCH_ON"; - private static final String ACTION_CLEAR = "com.android.wm.shell.flicker.testapp.CLEAR"; - - private final PictureInPictureParams.Builder mPipParamsBuilder = - new PictureInPictureParams.Builder() - .setAspectRatio(RATIO_DEFAULT); - private MediaSession mMediaSession; - private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder() - .setActions(ACTION_PLAY | ACTION_PAUSE | ACTION_STOP) - .setState(STATE_STOPPED, 0, 1f); - private PlaybackState mPlaybackState = mPlaybackStateBuilder.build(); - private final MediaMetadata.Builder mMediaMetadataBuilder = new MediaMetadata.Builder(); - - private final List<RemoteAction> mSwitchOffActions = new ArrayList<>(); - private final List<RemoteAction> mSwitchOnActions = new ArrayList<>(); - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (isInPictureInPictureMode()) { - switch (intent.getAction()) { - case ACTION_SWITCH_ON: - mPipParamsBuilder.setActions(mSwitchOnActions); - break; - case ACTION_SWITCH_OFF: - mPipParamsBuilder.setActions(mSwitchOffActions); - break; - case ACTION_CLEAR: - mPipParamsBuilder.setActions(Collections.emptyList()); - break; - case ACTION_NO_OP: - return; - default: - Log.w(TAG, "Unhandled action=" + intent.getAction()); - return; - } - setPictureInPictureParams(mPipParamsBuilder.build()); - } else { - switch (intent.getAction()) { - case ACTION_ENTER_PIP: - enterPip(null); - break; - case ACTION_SET_REQUESTED_ORIENTATION: - setRequestedOrientation(Integer.parseInt(intent.getStringExtra( - EXTRA_PIP_ORIENTATION))); - break; - default: - Log.w(TAG, "Unhandled action=" + intent.getAction()); - return; - } - } - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final Window window = getWindow(); - final WindowManager.LayoutParams layoutParams = window.getAttributes(); - layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - window.setAttributes(layoutParams); - - setContentView(R.layout.activity_pip); - - findViewById(R.id.media_session_start) - .setOnClickListener(v -> updateMediaSessionState(STATE_PLAYING)); - findViewById(R.id.media_session_stop) - .setOnClickListener(v -> updateMediaSessionState(STATE_STOPPED)); - - mMediaSession = new MediaSession(this, "WMShell_TestApp"); - mMediaSession.setPlaybackState(mPlaybackStateBuilder.build()); - mMediaSession.setCallback(new MediaSession.Callback() { - @Override - public void onPlay() { - updateMediaSessionState(STATE_PLAYING); - } - - @Override - public void onPause() { - updateMediaSessionState(STATE_PAUSED); - } - - @Override - public void onStop() { - updateMediaSessionState(STATE_STOPPED); - } - }); - - // Build two sets of the custom actions. We'll replace one with the other when 'On'/'Off' - // action is invoked. - // The first set consists of 3 actions: 1) Off; 2) No-Op; 3) Clear. - // The second set consists of 2 actions: 1) On; 2) Clear. - // Upon invocation 'Clear' action clear-off all the custom actions, including itself. - final Icon icon = Icon.createWithResource(this, android.R.drawable.ic_menu_help); - final RemoteAction noOpAction = buildRemoteAction(icon, PIP_ACTION_NO_OP, ACTION_NO_OP); - final RemoteAction switchOnAction = - buildRemoteAction(icon, PIP_ACTION_ON, ACTION_SWITCH_ON); - final RemoteAction switchOffAction = - buildRemoteAction(icon, PIP_ACTION_OFF, ACTION_SWITCH_OFF); - final RemoteAction clearAllAction = buildRemoteAction(icon, PIP_ACTION_CLEAR, ACTION_CLEAR); - mSwitchOffActions.addAll(Arrays.asList(switchOnAction, clearAllAction)); - mSwitchOnActions.addAll(Arrays.asList(noOpAction, switchOffAction, clearAllAction)); - - final IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_NO_OP); - filter.addAction(ACTION_SWITCH_ON); - filter.addAction(ACTION_SWITCH_OFF); - filter.addAction(ACTION_CLEAR); - filter.addAction(ACTION_SET_REQUESTED_ORIENTATION); - filter.addAction(ACTION_ENTER_PIP); - registerReceiver(mBroadcastReceiver, filter); - - handleIntentExtra(getIntent()); - } - - @Override - protected void onDestroy() { - unregisterReceiver(mBroadcastReceiver); - super.onDestroy(); - } - - @Override - protected void onUserLeaveHint() { - // Only used when auto PiP is disabled. This is to simulate the behavior that an app - // supports regular PiP but not auto PiP. - final boolean manuallyEnterPip = - ((RadioButton) findViewById(R.id.enter_pip_on_leave_manual)).isChecked(); - if (manuallyEnterPip) { - enterPictureInPictureMode(); - } - } - - private RemoteAction buildRemoteAction(Icon icon, String label, String action) { - final Intent intent = new Intent(action); - final PendingIntent pendingIntent = - PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - return new RemoteAction(icon, label, label, pendingIntent); - } - - public void enterPip(View v) { - final boolean withCustomActions = - ((CheckBox) findViewById(R.id.with_custom_actions)).isChecked(); - mPipParamsBuilder.setActions( - withCustomActions ? mSwitchOnActions : Collections.emptyList()); - enterPictureInPictureMode(mPipParamsBuilder.build()); - } - - public void onAutoPipSelected(View v) { - switch (v.getId()) { - case R.id.enter_pip_on_leave_manual: - // disable auto enter PiP - case R.id.enter_pip_on_leave_disabled: - mPipParamsBuilder.setAutoEnterEnabled(false); - setPictureInPictureParams(mPipParamsBuilder.build()); - break; - case R.id.enter_pip_on_leave_autoenter: - mPipParamsBuilder.setAutoEnterEnabled(true); - setPictureInPictureParams(mPipParamsBuilder.build()); - break; - } - } - - public void onRatioSelected(View v) { - switch (v.getId()) { - case R.id.ratio_default: - mPipParamsBuilder.setAspectRatio(RATIO_DEFAULT); - break; - - case R.id.ratio_square: - mPipParamsBuilder.setAspectRatio(RATIO_SQUARE); - break; - - case R.id.ratio_wide: - mPipParamsBuilder.setAspectRatio(RATIO_WIDE); - break; - - case R.id.ratio_tall: - mPipParamsBuilder.setAspectRatio(RATIO_TALL); - break; - } - } - - private void updateMediaSessionState(int newState) { - if (mPlaybackState.getState() == newState) { - return; - } - final String title; - switch (newState) { - case STATE_PLAYING: - title = TITLE_STATE_PLAYING; - break; - case STATE_PAUSED: - title = TITLE_STATE_PAUSED; - break; - case STATE_STOPPED: - title = ""; - break; - - default: - throw new IllegalArgumentException("Unknown state " + newState); - } - - mPlaybackStateBuilder.setState(newState, 0, 1f); - mPlaybackState = mPlaybackStateBuilder.build(); - - mMediaMetadataBuilder.putText(METADATA_KEY_TITLE, title); - - mMediaSession.setPlaybackState(mPlaybackState); - mMediaSession.setMetadata(mMediaMetadataBuilder.build()); - mMediaSession.setActive(newState != STATE_STOPPED); - } - - private void handleIntentExtra(Intent intent) { - // Set the fixed orientation if requested - if (intent.hasExtra(EXTRA_PIP_ORIENTATION)) { - final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_PIP_ORIENTATION)); - setRequestedOrientation(ori); - } - // Enter picture in picture with the given aspect ratio if provided - if (intent.hasExtra(EXTRA_ENTER_PIP)) { - mPipParamsBuilder.setActions(mSwitchOnActions); - enterPip(null); - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java deleted file mode 100644 index 8020ef2270a0..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2022 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.testapp; - -import android.app.Activity; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; - -public class SendNotificationActivity extends Activity { - private NotificationManager mNotificationManager; - private String mChannelId = "Channel id"; - private String mChannelName = "Channel name"; - private NotificationChannel mChannel; - private int mNotifyId = 0; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_notification); - findViewById(R.id.button_send_notification).setOnClickListener(this::sendNotification); - - mChannel = new NotificationChannel(mChannelId, mChannelName, - NotificationManager.IMPORTANCE_DEFAULT); - mNotificationManager = getSystemService(NotificationManager.class); - mNotificationManager.createNotificationChannel(mChannel); - } - - private void sendNotification(View v) { - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, - new Intent(this, SendNotificationActivity.class), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); - Notification notification = new Notification.Builder(this, mChannelId) - .setContentTitle("Notification App") - .setContentText("Notification content") - .setWhen(System.currentTimeMillis()) - .setSmallIcon(R.drawable.ic_message) - .setContentIntent(pendingIntent) - .build(); - - mNotificationManager.notify(mNotifyId, notification); - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java deleted file mode 100644 index 5343c1893d4e..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2018 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.testapp; - -import android.app.Activity; -import android.os.Bundle; -import android.view.WindowManager; - -public class SimpleActivity extends Activity { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); - setContentView(R.layout.activity_simple); - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java deleted file mode 100644 index baa1e6fdd1e9..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2020 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.testapp; - -import android.app.Activity; -import android.os.Bundle; - -public class SplitScreenSecondaryActivity extends Activity { - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - setContentView(R.layout.activity_splitscreen_secondary); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java index b5ee037892ba..51a20ee9d090 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java @@ -17,8 +17,10 @@ package com.android.wm.shell; import static android.view.Display.DEFAULT_DISPLAY; +import static org.junit.Assume.assumeTrue; import android.content.Context; +import android.content.pm.PackageManager; import android.hardware.display.DisplayManager; import android.testing.TestableContext; @@ -36,6 +38,7 @@ import org.mockito.MockitoAnnotations; public abstract class ShellTestCase { protected TestableContext mContext; + private PackageManager mPm; @Before public void shellSetup() { @@ -46,6 +49,7 @@ public abstract class ShellTestCase { final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final DisplayManager dm = context.getSystemService(DisplayManager.class); + mPm = context.getPackageManager(); mContext = new TestableContext( context.createDisplayContext(dm.getDisplay(DEFAULT_DISPLAY))); @@ -66,4 +70,20 @@ public abstract class ShellTestCase { protected Context getContext() { return mContext; } + + /** + * Makes an assumption that the test device is a TV device, used to guard tests that should + * only be run on TVs. + */ + protected void assumeTelevision() { + assumeTrue(isTelevision()); + } + + /** + * Returns whether this test device is a TV device. + */ + protected boolean isTelevision() { + return mPm.hasSystemFeature(PackageManager.FEATURE_LEANBACK) + || mPm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java index fe8b305093d7..da95c77d2b89 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java @@ -48,10 +48,9 @@ public class TestShellExecutor implements ShellExecutor { } public void flushAll() { - final ArrayList<Runnable> tmpRunnable = new ArrayList<>(mRunnables); - mRunnables.clear(); - for (Runnable r : tmpRunnable) { + for (Runnable r : mRunnables) { r.run(); } + mRunnables.clear(); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 2754496a6f3f..8a5b4901ed96 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -18,13 +18,12 @@ package com.android.wm.shell.back; import static android.window.BackNavigationInfo.KEY_TRIGGER_BACK; -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.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -39,7 +38,7 @@ import android.app.WindowConfiguration; import android.content.pm.ApplicationInfo; import android.graphics.Point; import android.graphics.Rect; -import android.hardware.HardwareBuffer; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteCallback; @@ -49,15 +48,17 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableContentResolver; import android.testing.TestableContext; import android.testing.TestableLooper; +import android.view.IRemoteAnimationRunner; import android.view.MotionEvent; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.BackEvent; import android.window.BackMotionEvent; import android.window.BackNavigationInfo; -import android.window.IBackNaviAnimationController; +import android.window.IBackAnimationFinishedCallback; import android.window.IOnBackInvokedCallback; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -69,7 +70,6 @@ import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.sysui.ShellSharedConstants; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -94,23 +94,27 @@ public class BackAnimationControllerTest extends ShellTestCase { new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()); @Mock - private SurfaceControl.Transaction mTransaction; + private IActivityTaskManager mActivityTaskManager; @Mock - private IActivityTaskManager mActivityTaskManager; + private IOnBackInvokedCallback mAppCallback; @Mock - private IOnBackInvokedCallback mIOnBackInvokedCallback; + private IOnBackInvokedCallback mAnimatorCallback; @Mock - private IBackNaviAnimationController mIBackNaviAnimationController; + private IBackAnimationFinishedCallback mBackAnimationFinishedCallback; + + @Mock + private IRemoteAnimationRunner mBackAnimationRunner; @Mock private ShellController mShellController; - private BackAnimationController mController; + @Mock + private BackAnimationBackground mAnimationBackground; - private int mEventTime = 0; + private BackAnimationController mController; private TestableContentResolver mContentResolver; private TestableLooper mTestableLooper; @@ -125,29 +129,20 @@ public class BackAnimationControllerTest extends ShellTestCase { mTestableLooper = TestableLooper.get(this); mShellInit = spy(new ShellInit(mShellExecutor)); mController = new BackAnimationController(mShellInit, mShellController, - mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction, + mShellExecutor, new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, - mContentResolver); + mContentResolver, mAnimationBackground); mController.setEnableUAnimation(true); mShellInit.init(); - mEventTime = 0; mShellExecutor.flushAll(); } - private void createNavigationInfo(RemoteAnimationTarget topAnimationTarget, - SurfaceControl screenshotSurface, - HardwareBuffer hardwareBuffer, - int backType, - IOnBackInvokedCallback onBackInvokedCallback, boolean prepareAnimation) { + private void createNavigationInfo(int backType, boolean enableAnimation) { BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() .setType(backType) - .setDepartingAnimationTarget(topAnimationTarget) - .setScreenshotSurface(screenshotSurface) - .setScreenshotBuffer(hardwareBuffer) - .setTaskWindowConfiguration(new WindowConfiguration()) .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) - .setOnBackInvokedCallback(onBackInvokedCallback) - .setPrepareAnimation(prepareAnimation); + .setOnBackInvokedCallback(mAppCallback) + .setPrepareRemoteAnimation(enableAnimation); createNavigationInfo(builder); } @@ -155,7 +150,7 @@ public class BackAnimationControllerTest extends ShellTestCase { private void createNavigationInfo(BackNavigationInfo.Builder builder) { try { doReturn(builder.build()).when(mActivityTaskManager) - .startBackNavigation(anyBoolean(), any(), any()); + .startBackNavigation(any(), any()); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } @@ -188,75 +183,57 @@ public class BackAnimationControllerTest extends ShellTestCase { } @Test - @Ignore("b/207481538") - public void crossActivity_screenshotAttachedAndVisible() { - SurfaceControl screenshotSurface = new SurfaceControl(); - HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); - createNavigationInfo(createAnimationTarget(), screenshotSurface, hardwareBuffer, - BackNavigationInfo.TYPE_CROSS_ACTIVITY, null, true); - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer); - verify(mTransaction).setVisibility(screenshotSurface, true); - verify(mTransaction).apply(); - } + public void verifyNavigationFinishes() throws RemoteException { + final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME, + BackNavigationInfo.TYPE_CROSS_TASK, + BackNavigationInfo.TYPE_CROSS_ACTIVITY, + BackNavigationInfo.TYPE_DIALOG_CLOSE, + BackNavigationInfo.TYPE_CALLBACK }; + + for (int type: testTypes) { + registerAnimation(type); + } - @Test - public void crossActivity_surfaceMovesWithGesture() { - SurfaceControl screenshotSurface = new SurfaceControl(); - HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); - RemoteAnimationTarget animationTarget = createAnimationTarget(); - createNavigationInfo(animationTarget, screenshotSurface, hardwareBuffer, - BackNavigationInfo.TYPE_CROSS_ACTIVITY, null, true); - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - doMotionEvent(MotionEvent.ACTION_MOVE, 100); - // b/207481538, we check that the surface is not moved for now, we can re-enable this once - // we implement the animation - verify(mTransaction, never()).setScale(eq(screenshotSurface), anyInt(), anyInt()); - verify(mTransaction, never()).setPosition( - animationTarget.leash, 100, 100); - verify(mTransaction, atLeastOnce()).apply(); - } + for (int type: testTypes) { + final ResultListener result = new ResultListener(); + createNavigationInfo(new BackNavigationInfo.Builder() + .setType(type) + .setOnBackInvokedCallback(mAppCallback) + .setPrepareRemoteAnimation(true) + .setOnBackNavigationDone(new RemoteCallback(result))); + triggerBackGesture(); + simulateRemoteAnimationStart(type); + simulateRemoteAnimationFinished(); + mShellExecutor.flushAll(); - @Test - public void verifyAnimationFinishes() { - RemoteAnimationTarget animationTarget = createAnimationTarget(); - boolean[] backNavigationDone = new boolean[]{false}; - boolean[] triggerBack = new boolean[]{false}; - createNavigationInfo(new BackNavigationInfo.Builder() - .setDepartingAnimationTarget(animationTarget) - .setType(BackNavigationInfo.TYPE_CROSS_ACTIVITY) - .setOnBackNavigationDone( - new RemoteCallback(result -> { - backNavigationDone[0] = true; - triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK); - }))); - triggerBackGesture(); - assertTrue("Navigation Done callback not called", backNavigationDone[0]); - assertTrue("TriggerBack should have been true", triggerBack[0]); + assertTrue("Navigation Done callback not called for " + + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); + assertTrue("TriggerBack should have been true", result.mTriggerBack); + } } @Test public void backToHome_dispatchesEvents() throws RemoteException { - mController.setBackToLauncherCallback(mIOnBackInvokedCallback); - RemoteAnimationTarget animationTarget = createAnimationTarget(); - createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true); + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); - simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); + + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + + verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class)); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); ArgumentCaptor<BackMotionEvent> backEventCaptor = ArgumentCaptor.forClass(BackMotionEvent.class); - verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture()); - assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget()); - verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackMotionEvent.class)); + verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture()); // Check that back invocation is dispatched. mController.setTriggerBack(true); // Fake trigger back doMotionEvent(MotionEvent.ACTION_UP, 0); - verify(mIOnBackInvokedCallback).onBackInvoked(); + verify(mAnimatorCallback).onBackInvoked(); } @Test @@ -265,94 +242,100 @@ public class BackAnimationControllerTest extends ShellTestCase { Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0"); ShellInit shellInit = new ShellInit(mShellExecutor); mController = new BackAnimationController(shellInit, mShellController, - mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction, + mShellExecutor, new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, - mContentResolver); + mContentResolver, mAnimationBackground); shellInit.init(); - mController.setBackToLauncherCallback(mIOnBackInvokedCallback); + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - RemoteAnimationTarget animationTarget = createAnimationTarget(); - IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class); ArgumentCaptor<BackMotionEvent> backEventCaptor = ArgumentCaptor.forClass(BackMotionEvent.class); - createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback, false); + + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false); triggerBackGesture(); - verify(appCallback, never()).onBackStarted(any(BackMotionEvent.class)); - verify(appCallback, never()).onBackProgressed(backEventCaptor.capture()); - verify(appCallback, times(1)).onBackInvoked(); + verify(mAppCallback, never()).onBackStarted(any()); + verify(mAppCallback, never()).onBackProgressed(backEventCaptor.capture()); + verify(mAppCallback, times(1)).onBackInvoked(); - verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackMotionEvent.class)); - verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture()); - verify(mIOnBackInvokedCallback, never()).onBackInvoked(); + verify(mAnimatorCallback, never()).onBackStarted(any()); + verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture()); + verify(mAnimatorCallback, never()).onBackInvoked(); + verify(mBackAnimationRunner, never()).onAnimationStart( + anyInt(), any(), any(), any(), any()); } @Test public void ignoresGesture_transitionInProgress() throws RemoteException { - mController.setBackToLauncherCallback(mIOnBackInvokedCallback); - RemoteAnimationTarget animationTarget = createAnimationTarget(); - createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true); + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); triggerBackGesture(); - simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); // Check that back invocation is dispatched. - verify(mIOnBackInvokedCallback).onBackInvoked(); + verify(mAnimatorCallback).onBackInvoked(); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); + + reset(mAnimatorCallback); + reset(mBackAnimationRunner); - reset(mIOnBackInvokedCallback); // Verify that we prevent animation from restarting if another gestures happens before // the previous transition is finished. doMotionEvent(MotionEvent.ACTION_DOWN, 0); - verifyNoMoreInteractions(mIOnBackInvokedCallback); - mController.onBackToLauncherAnimationFinished(); + verifyNoMoreInteractions(mAnimatorCallback); + + // Finish back navigation. + simulateRemoteAnimationFinished(); // Verify that more events from a rejected swipe cannot start animation. doMotionEvent(MotionEvent.ACTION_MOVE, 100); doMotionEvent(MotionEvent.ACTION_UP, 0); - verifyNoMoreInteractions(mIOnBackInvokedCallback); + verifyNoMoreInteractions(mAnimatorCallback); // Verify that we start accepting gestures again once transition finishes. doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); - simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class)); + + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + verify(mAnimatorCallback).onBackStarted(any()); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); } @Test public void acceptsGesture_transitionTimeout() throws RemoteException { - mController.setBackToLauncherCallback(mIOnBackInvokedCallback); - RemoteAnimationTarget animationTarget = createAnimationTarget(); - createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true); + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); + + // In case it is still running in animation. + doNothing().when(mAnimatorCallback).onBackInvoked(); triggerBackGesture(); - simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - reset(mIOnBackInvokedCallback); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); // Simulate transition timeout. mShellExecutor.flushAll(); + reset(mAnimatorCallback); + doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); - simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class)); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + verify(mAnimatorCallback).onBackStarted(any()); } - @Test public void cancelBackInvokeWhenLostFocus() throws RemoteException { - mController.setBackToLauncherCallback(mIOnBackInvokedCallback); - RemoteAnimationTarget animationTarget = createAnimationTarget(); + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); - simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class)); + + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + verify(mAnimatorCallback).onBackStarted(any()); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); // Check that back invocation is dispatched. mController.setTriggerBack(true); // Fake trigger back @@ -361,11 +344,74 @@ public class BackAnimationControllerTest extends ShellTestCase { IBinder token = mock(IBinder.class); mController.mFocusObserver.focusLost(token); mShellExecutor.flushAll(); - verify(mIOnBackInvokedCallback).onBackCancelled(); + verify(mAnimatorCallback).onBackCancelled(); // No more back invoke. doMotionEvent(MotionEvent.ACTION_UP, 0); - verify(mIOnBackInvokedCallback, never()).onBackInvoked(); + verify(mAnimatorCallback, never()).onBackInvoked(); + } + + @Test + public void animationNotDefined() throws RemoteException { + final int[] testTypes = new int[] { + BackNavigationInfo.TYPE_RETURN_TO_HOME, + BackNavigationInfo.TYPE_CROSS_TASK, + BackNavigationInfo.TYPE_CROSS_ACTIVITY, + BackNavigationInfo.TYPE_DIALOG_CLOSE}; + + for (int type: testTypes) { + unregisterAnimation(type); + } + + for (int type: testTypes) { + final ResultListener result = new ResultListener(); + createNavigationInfo(new BackNavigationInfo.Builder() + .setType(type) + .setOnBackInvokedCallback(mAppCallback) + .setPrepareRemoteAnimation(true) + .setOnBackNavigationDone(new RemoteCallback(result))); + triggerBackGesture(); + simulateRemoteAnimationStart(type); + mShellExecutor.flushAll(); + + assertTrue("Navigation Done callback not called for " + + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); + assertTrue("TriggerBack should have been true", result.mTriggerBack); + } + + verify(mAppCallback, never()).onBackStarted(any()); + verify(mAppCallback, never()).onBackProgressed(any()); + verify(mAppCallback, times(testTypes.length)).onBackInvoked(); + + verify(mAnimatorCallback, never()).onBackStarted(any()); + verify(mAnimatorCallback, never()).onBackProgressed(any()); + verify(mAnimatorCallback, never()).onBackInvoked(); + } + + @Test + public void callbackShouldDeliverProgress() throws RemoteException { + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); + + final int type = BackNavigationInfo.TYPE_CALLBACK; + final ResultListener result = new ResultListener(); + createNavigationInfo(new BackNavigationInfo.Builder() + .setType(type) + .setOnBackInvokedCallback(mAppCallback) + .setOnBackNavigationDone(new RemoteCallback(result))); + triggerBackGesture(); + mShellExecutor.flushAll(); + + assertTrue("Navigation Done callback not called for " + + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); + assertTrue("TriggerBack should have been true", result.mTriggerBack); + + verify(mAppCallback, times(1)).onBackStarted(any()); + verify(mAppCallback, times(1)).onBackProgressed(any()); + verify(mAppCallback, times(1)).onBackInvoked(); + + verify(mAnimatorCallback, never()).onBackStarted(any()); + verify(mAnimatorCallback, never()).onBackProgressed(any()); + verify(mAnimatorCallback, never()).onBackInvoked(); } private void doMotionEvent(int actionDown, int coordinate) { @@ -373,16 +419,39 @@ public class BackAnimationControllerTest extends ShellTestCase { coordinate, coordinate, actionDown, BackEvent.EDGE_LEFT); - mEventTime += 10; } - private void simulateRemoteAnimationStart(int type, RemoteAnimationTarget animationTarget) - throws RemoteException { - if (mController.mIBackAnimationRunner != null) { - final RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget}; - mController.mIBackAnimationRunner.onAnimationStart(mIBackNaviAnimationController, type, - targets, null, null); + private void simulateRemoteAnimationStart(int type) throws RemoteException { + RemoteAnimationTarget animationTarget = createAnimationTarget(); + RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget}; + if (mController.mBackAnimationAdapter != null) { + mController.mBackAnimationAdapter.getRunner().onAnimationStart(type, + targets, null, null, mBackAnimationFinishedCallback); mShellExecutor.flushAll(); } } + + private void simulateRemoteAnimationFinished() { + mController.onBackAnimationFinished(); + mController.finishBackNavigation(); + } + + private void registerAnimation(int type) { + mController.registerAnimation(type, + new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner)); + } + + private void unregisterAnimation(int type) { + mController.unregisterAnimation(type); + } + + private static class ResultListener implements RemoteCallback.OnResultListener { + boolean mBackNavigationDone = false; + boolean mTriggerBack = false; + @Override + public void onResult(@Nullable Bundle result) { + mBackNavigationDone = true; + mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK); + } + }; } 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 new file mode 100644 index 000000000000..3608474bd90e --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 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.back; + +import static android.window.BackEvent.EDGE_LEFT; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.os.Handler; +import android.os.Looper; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.window.BackEvent; +import android.window.BackMotionEvent; +import android.window.BackProgressAnimator; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class BackProgressAnimatorTest { + private BackProgressAnimator mProgressAnimator; + private BackEvent mReceivedBackEvent; + private float mTargetProgress = 0.5f; + private CountDownLatch mTargetProgressCalled = new CountDownLatch(1); + private Handler mMainThreadHandler; + + @Before + public void setUp() throws Exception { + mMainThreadHandler = new Handler(Looper.getMainLooper()); + final BackMotionEvent backEvent = new BackMotionEvent( + 0, 0, + 0, EDGE_LEFT, null); + mMainThreadHandler.post( + () -> { + mProgressAnimator = new BackProgressAnimator(); + mProgressAnimator.onBackStarted(backEvent, this::onGestureProgress); + }); + } + + @Test + public void testBackProgressed() throws InterruptedException { + final BackMotionEvent backEvent = new BackMotionEvent( + 100, 0, + mTargetProgress, EDGE_LEFT, null); + mMainThreadHandler.post( + () -> mProgressAnimator.onBackProgressed(backEvent)); + + mTargetProgressCalled.await(1, TimeUnit.SECONDS); + + assertNotNull(mReceivedBackEvent); + assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */); + } + + @Test + public void testBackCancelled() throws InterruptedException { + // Give the animator some progress. + final BackMotionEvent backEvent = new BackMotionEvent( + 100, 0, + mTargetProgress, EDGE_LEFT, null); + mMainThreadHandler.post( + () -> mProgressAnimator.onBackProgressed(backEvent)); + mTargetProgressCalled.await(1, TimeUnit.SECONDS); + assertNotNull(mReceivedBackEvent); + + // Trigger animation cancel, the target progress should be 0. + mTargetProgress = 0; + mTargetProgressCalled = new CountDownLatch(1); + CountDownLatch cancelCallbackCalled = new CountDownLatch(1); + mMainThreadHandler.post( + () -> mProgressAnimator.onBackCancelled(() -> cancelCallbackCalled.countDown())); + cancelCallbackCalled.await(1, TimeUnit.SECONDS); + mTargetProgressCalled.await(1, TimeUnit.SECONDS); + assertNotNull(mReceivedBackEvent); + assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */); + } + + private void onGestureProgress(BackEvent backEvent) { + if (mTargetProgress == backEvent.getProgress()) { + mReceivedBackEvent = backEvent; + mTargetProgressCalled.countDown(); + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index 262ee72d86fc..ffb1a4d66f1e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -41,7 +41,6 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; -import com.android.internal.view.IInputMethodManager; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.sysui.ShellInit; @@ -58,8 +57,6 @@ public class DisplayImeControllerTest extends ShellTestCase { @Mock private SurfaceControl.Transaction mT; @Mock - private IInputMethodManager mMock; - @Mock private ShellInit mShellInit; private DisplayImeController.PerDisplay mPerDisplay; private Executor mExecutor; @@ -79,10 +76,6 @@ public class DisplayImeControllerTest extends ShellTestCase { } }, mExecutor) { @Override - public IInputMethodManager getImms() { - return mMock; - } - @Override void removeImeSurface() { } }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0); } @@ -106,13 +99,13 @@ public class DisplayImeControllerTest extends ShellTestCase { @Test public void showInsets_schedulesNoWorkOnExecutor() { - mPerDisplay.showInsets(ime(), true); + mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */); verifyZeroInteractions(mExecutor); } @Test public void hideInsets_schedulesNoWorkOnExecutor() { - mPerDisplay.hideInsets(ime(), true); + mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */); verifyZeroInteractions(mExecutor); } @@ -145,13 +138,14 @@ public class DisplayImeControllerTest extends ShellTestCase { private InsetsSourceControl[] insetsSourceControl() { return new InsetsSourceControl[]{ new InsetsSourceControl( - ITYPE_IME, mock(SurfaceControl.class), false, new Point(0, 0), Insets.NONE) + ITYPE_IME, ime(), mock(SurfaceControl.class), false, new Point(0, 0), + Insets.NONE) }; } private InsetsState insetsStateWithIme(boolean visible) { InsetsState state = new InsetsState(); - state.addSource(new InsetsSource(ITYPE_IME)); + state.addSource(new InsetsSource(ITYPE_IME, ime())); state.setSourceVisible(ITYPE_IME, visible); return state; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java index 5f5a3c584ee0..956f1cd419c2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java @@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.annotation.Nullable; import android.content.ComponentName; import android.os.RemoteException; import android.util.SparseArray; @@ -32,7 +33,8 @@ import android.view.IDisplayWindowInsetsController; import android.view.IWindowManager; import android.view.InsetsSourceControl; import android.view.InsetsState; -import android.view.InsetsVisibilities; +import android.view.WindowInsets; +import android.view.inputmethod.ImeTracker; import androidx.test.filters.SmallTest; @@ -108,11 +110,13 @@ public class DisplayInsetsControllerTest extends ShellTestCase { mController.addInsetsChangedListener(SECOND_DISPLAY, secondListener); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).topFocusedWindowChanged(null, - new InsetsVisibilities()); + WindowInsets.Type.defaultVisible()); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null); - mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false); - mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false, + null /* statsToken */); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false, + null /* statsToken */); mExecutor.flushAll(); assertTrue(defaultListener.topFocusedWindowChangedCount == 1); @@ -128,11 +132,13 @@ public class DisplayInsetsControllerTest extends ShellTestCase { assertTrue(secondListener.hideInsetsCount == 0); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).topFocusedWindowChanged(null, - new InsetsVisibilities()); + WindowInsets.Type.defaultVisible()); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null); - mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false); - mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false, + null /* statsToken */); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false, + null /* statsToken */); mExecutor.flushAll(); assertTrue(defaultListener.topFocusedWindowChangedCount == 1); @@ -175,8 +181,7 @@ public class DisplayInsetsControllerTest extends ShellTestCase { int hideInsetsCount = 0; @Override - public void topFocusedWindowChanged(ComponentName component, - InsetsVisibilities requestedVisibilities) { + public void topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes) { topFocusedWindowChangedCount++; } @@ -192,12 +197,12 @@ public class DisplayInsetsControllerTest extends ShellTestCase { } @Override - public void showInsets(int types, boolean fromIme) { + public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { showInsetsCount++; } @Override - public void hideInsets(int types, boolean fromIme) { + public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { hideInsetsCount++; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 2fc0914acbd4..d4408d78eba1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -19,6 +19,7 @@ package com.android.wm.shell.compatui; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; +import static android.view.WindowInsets.Type.navigationBars; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -291,7 +292,7 @@ public class CompatUIControllerTest extends ShellTestCase { mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); InsetsState insetsState = new InsetsState(); - InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); + InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars()); insetsSource.setFrame(0, 0, 1000, 1000); insetsState.addSource(insetsSource); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index e79b803b4304..c4d78bbb2004 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -21,6 +21,7 @@ import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; +import static android.view.WindowInsets.Type.navigationBars; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -328,7 +329,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { // Update if the insets change on the existing display layout clearInvocations(mWindowManager); InsetsState insetsState = new InsetsState(); - InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); + InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars()); insetsSource.setFrame(0, 0, 1000, 1000); insetsState.addSource(insetsSource); displayLayout.setInsets(mContext.getResources(), insetsState); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS new file mode 100644 index 000000000000..736d4cff6ce8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS @@ -0,0 +1,3 @@ +# WM shell sub-module TV pip owners +galinap@google.com +bronger@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java new file mode 100644 index 000000000000..e5b61ed2fd25 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2022 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.pip.tv; + +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN; +import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.graphics.drawable.Icon; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.Log; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.pip.PipMediaController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit tests for {@link TvPipActionsProvider} + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class TvPipActionProviderTest extends ShellTestCase { + private static final String TAG = TvPipActionProviderTest.class.getSimpleName(); + private TvPipActionsProvider mActionsProvider; + + @Mock + private PipMediaController mMockPipMediaController; + @Mock + private TvPipActionsProvider.Listener mMockListener; + @Mock + private TvPipAction.SystemActionsHandler mMockSystemActionsHandler; + @Mock + private Icon mMockIcon; + @Mock + private PendingIntent mMockPendingIntent; + + private RemoteAction createRemoteAction(int identifier) { + return new RemoteAction(mMockIcon, "" + identifier, "" + identifier, mMockPendingIntent); + } + + private List<RemoteAction> createRemoteActions(int numberOfActions) { + List<RemoteAction> actions = new ArrayList<>(); + for (int i = 0; i < numberOfActions; i++) { + actions.add(createRemoteAction(i)); + } + return actions; + } + + private boolean checkActionsMatch(List<TvPipAction> actions, int[] actionTypes) { + for (int i = 0; i < actions.size(); i++) { + int type = actions.get(i).getActionType(); + if (type != actionTypes[i]) { + Log.e(TAG, "Action at index " + i + ": found " + type + + ", expected " + actionTypes[i]); + return false; + } + } + return true; + } + + @Before + public void setUp() { + if (!isTelevision()) { + return; + } + MockitoAnnotations.initMocks(this); + mActionsProvider = new TvPipActionsProvider(mContext, mMockPipMediaController, + mMockSystemActionsHandler); + } + + @Test + public void defaultSystemActions_regularPip() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE})); + } + + @Test + public void defaultSystemActions_expandedPip() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(true); + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE})); + } + + @Test + public void expandedPip_enableExpansion_enable() { + assumeTelevision(); + // PiP has expanded PiP disabled. + mActionsProvider.updateExpansionEnabled(false); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.updateExpansionEnabled(true); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE})); + verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 0, /* index= */ 3); + } + + @Test + public void expandedPip_enableExpansion_disable() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(true); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.updateExpansionEnabled(false); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE})); + verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 0, /* index= */ 3); + } + + @Test + public void expandedPip_enableExpansion_AlreadyEnabled() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(true); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.updateExpansionEnabled(true); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE})); + } + + @Test + public void expandedPip_toggleExpansion() { + assumeTelevision(); + // PiP has expanded PiP enabled, but is in a collapsed state + mActionsProvider.updateExpansionEnabled(true); + mActionsProvider.onPipExpansionToggled(/* expanded= */ false); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.onPipExpansionToggled(/* expanded= */ true); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE})); + verify(mMockListener).onActionsChanged(0, 1, 3); + } + + @Test + public void customActions_added() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + mActionsProvider.addListener(mMockListener); + + mActionsProvider.setAppActions(createRemoteActions(2), null); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE})); + verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2); + } + + @Test + public void customActions_replacedMore() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + mActionsProvider.setAppActions(createRemoteActions(2), null); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.setAppActions(createRemoteActions(3), null); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_CUSTOM, ACTION_MOVE})); + verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 2, /* index= */ 2); + } + + @Test + public void customActions_replacedLess() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + mActionsProvider.setAppActions(createRemoteActions(2), null); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.setAppActions(createRemoteActions(0), null); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE})); + verify(mMockListener).onActionsChanged(/* added= */ -2, /* updated= */ 0, /* index= */ 2); + } + + @Test + public void customCloseAdded() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + + List<RemoteAction> customActions = new ArrayList<>(); + mActionsProvider.setAppActions(customActions, null); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.setAppActions(customActions, createRemoteAction(0)); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE})); + verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1); + } + + @Test + public void customClose_matchesOtherCustomAction() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + + List<RemoteAction> customActions = createRemoteActions(2); + RemoteAction customClose = createRemoteAction(/* id= */ 10); + customActions.add(customClose); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.setAppActions(customActions, customClose); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE})); + verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1); + verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2); + } + + @Test + public void mediaActions_added_whileCustomActionsExist() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + mActionsProvider.setAppActions(createRemoteActions(2), null); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.onMediaActionsChanged(createRemoteActions(3)); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE})); + verify(mMockListener, times(0)).onActionsChanged(anyInt(), anyInt(), anyInt()); + } + + @Test + public void customActions_removed_whileMediaActionsExist() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + mActionsProvider.onMediaActionsChanged(createRemoteActions(2)); + mActionsProvider.setAppActions(createRemoteActions(3), null); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.setAppActions(createRemoteActions(0), null); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE})); + verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 2, /* index= */ 2); + } + + @Test + public void customCloseOnly_mediaActionsShowing() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + mActionsProvider.onMediaActionsChanged(createRemoteActions(2)); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.setAppActions(createRemoteActions(0), createRemoteAction(5)); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE})); + verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1); + } + + @Test + public void customActions_showDisabledActions() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + + List<RemoteAction> customActions = createRemoteActions(2); + customActions.get(0).setEnabled(false); + mActionsProvider.setAppActions(customActions, null); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE})); + } + + @Test + public void mediaActions_hideDisabledActions() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + + List<RemoteAction> customActions = createRemoteActions(2); + customActions.get(0).setEnabled(false); + mActionsProvider.onMediaActionsChanged(customActions); + + assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), + new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE})); + } + +} + diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt index 05e472245b4a..7370ed71bbdd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt @@ -24,6 +24,7 @@ import android.os.test.TestLooper import android.testing.AndroidTestingRunner import com.android.wm.shell.R +import com.android.wm.shell.ShellTestCase import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT import com.android.wm.shell.pip.tv.TvPipBoundsController.POSITION_DEBOUNCE_TIMEOUT_MILLIS import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement @@ -43,7 +44,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) -class TvPipBoundsControllerTest { +class TvPipBoundsControllerTest : ShellTestCase() { val ANIMATION_DURATION = 100 val STASH_DURATION = 5000 val FAR_FUTURE = 60 * 60000L @@ -71,7 +72,7 @@ class TvPipBoundsControllerTest { var inMoveMode = false @Mock - lateinit var context: Context + lateinit var mockContext: Context @Mock lateinit var resources: Resources @Mock @@ -83,6 +84,9 @@ class TvPipBoundsControllerTest { @Before fun setUp() { + if (!isTelevision) { + return + } MockitoAnnotations.initMocks(this) time = 0L inMenu = false @@ -91,13 +95,13 @@ class TvPipBoundsControllerTest { testLooper = TestLooper { time } mainHandler = Handler(testLooper.getLooper()) - whenever(context.resources).thenReturn(resources) + whenever(mockContext.resources).thenReturn(resources) whenever(resources.getInteger(R.integer.config_pipStashDuration)).thenReturn(STASH_DURATION) whenever(tvPipBoundsAlgorithm.adjustBoundsForTemporaryDecor(any())) .then(returnsFirstArg<Rect>()) boundsController = TvPipBoundsController( - context, + mockContext, { time }, mainHandler, tvPipBoundsState, @@ -107,6 +111,7 @@ class TvPipBoundsControllerTest { @Test fun testPlacement_MovedAfterDebounceTimeout() { + assumeTelevision() triggerPlacement(MOVED_PLACEMENT) assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS) assertNoMovementUpTo(time + FAR_FUTURE) @@ -114,6 +119,7 @@ class TvPipBoundsControllerTest { @Test fun testStashedPlacement_MovedAfterDebounceTimeout_Unstashes() { + assumeTelevision() triggerPlacement(STASHED_PLACEMENT_RESTASH) assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS + STASH_DURATION, ANCHOR_BOUNDS) @@ -121,6 +127,7 @@ class TvPipBoundsControllerTest { @Test fun testDebounceSamePlacement_MovesDebounceTimeoutAfterFirstPlacement() { + assumeTelevision() triggerPlacement(MOVED_PLACEMENT) advanceTimeTo(POSITION_DEBOUNCE_TIMEOUT_MILLIS / 2) triggerPlacement(MOVED_PLACEMENT) @@ -130,6 +137,7 @@ class TvPipBoundsControllerTest { @Test fun testNoMovementUntilPlacementStabilizes() { + assumeTelevision() triggerPlacement(ANCHOR_PLACEMENT) advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10) triggerPlacement(MOVED_PLACEMENT) @@ -143,6 +151,7 @@ class TvPipBoundsControllerTest { @Test fun testUnstashIfStashNoLongerNecessary() { + assumeTelevision() triggerPlacement(STASHED_PLACEMENT_RESTASH) assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) @@ -152,6 +161,7 @@ class TvPipBoundsControllerTest { @Test fun testRestashingPlacementDelaysUnstash() { + assumeTelevision() triggerPlacement(STASHED_PLACEMENT_RESTASH) assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) @@ -163,6 +173,7 @@ class TvPipBoundsControllerTest { @Test fun testNonRestashingPlacementDoesNotDelayUnstash() { + assumeTelevision() triggerPlacement(STASHED_PLACEMENT_RESTASH) assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) @@ -173,13 +184,26 @@ class TvPipBoundsControllerTest { @Test fun testImmediatePlacement() { + assumeTelevision() triggerImmediatePlacement(STASHED_PLACEMENT_RESTASH) assertMovement(STASHED_BOUNDS) assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS) } @Test + fun testImmediatePlacement_DoNotStashIfAlreadyUnstashed() { + assumeTelevision() + triggerImmediatePlacement(STASHED_PLACEMENT_RESTASH) + assertMovement(STASHED_BOUNDS) + assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS) + + triggerImmediatePlacement(STASHED_PLACEMENT) + assertNoMovementUpTo(time + FAR_FUTURE) + } + + @Test fun testInMoveMode_KeepAtAnchor() { + assumeTelevision() startMoveMode() triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH) assertMovement(ANCHOR_BOUNDS) @@ -188,6 +212,7 @@ class TvPipBoundsControllerTest { @Test fun testInMenu_Unstashed() { + assumeTelevision() openPipMenu() triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH) assertMovement(MOVED_BOUNDS) @@ -196,6 +221,7 @@ class TvPipBoundsControllerTest { @Test fun testCloseMenu_DoNotRestash() { + assumeTelevision() openPipMenu() triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH) assertMovement(MOVED_BOUNDS) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java new file mode 100644 index 000000000000..51f86b845a9f --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2022 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.pip.tv; + +import static android.view.KeyEvent.KEYCODE_DPAD_DOWN; +import static android.view.KeyEvent.KEYCODE_DPAD_LEFT; +import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT; +import static android.view.KeyEvent.KEYCODE_DPAD_UP; + +import static org.junit.Assert.assertEquals; + +import android.view.Gravity; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.pip.PipSnapAlgorithm; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Locale; + +public class TvPipGravityTest extends ShellTestCase { + + private static final float VERTICAL_EXPANDED_ASPECT_RATIO = 1f / 3; + private static final float HORIZONTAL_EXPANDED_ASPECT_RATIO = 3f; + + @Mock + private PipSnapAlgorithm mMockPipSnapAlgorithm; + + private TvPipBoundsState mTvPipBoundsState; + private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; + + @Before + public void setUp() { + if (!isTelevision()) { + return; + } + MockitoAnnotations.initMocks(this); + mTvPipBoundsState = new TvPipBoundsState(mContext); + mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState, + mMockPipSnapAlgorithm); + + setRTL(false); + } + + private void checkGravity(int gravityActual, int gravityExpected) { + assertEquals(gravityExpected, gravityActual); + } + + private void setRTL(boolean isRtl) { + mContext.getResources().getConfiguration().setLayoutDirection( + isRtl ? new Locale("ar") : Locale.ENGLISH); + mTvPipBoundsState.onConfigurationChanged(); + mTvPipBoundsAlgorithm.onConfigurationChanged(mContext); + } + + private void assertGravityAfterExpansion(int gravityFrom, int gravityTo) { + mTvPipBoundsState.setTvPipExpanded(false); + mTvPipBoundsState.setTvPipGravity(gravityFrom); + mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true); + checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo); + } + + private void assertGravityAfterCollapse(int gravityFrom, int gravityTo) { + mTvPipBoundsState.setTvPipExpanded(true); + mTvPipBoundsState.setTvPipGravity(gravityFrom); + mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false); + checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo); + } + + private void assertGravityAfterExpandAndCollapse(int gravityStartAndEnd) { + mTvPipBoundsState.setTvPipGravity(gravityStartAndEnd); + mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true); + mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false); + checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityStartAndEnd); + } + + @Test + public void regularPip_defaultGravity() { + assumeTelevision(); + checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.RIGHT | Gravity.BOTTOM); + } + + @Test + public void regularPip_defaultGravity_RTL() { + assumeTelevision(); + setRTL(true); + checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.LEFT | Gravity.BOTTOM); + } + + @Test + public void updateGravity_expand_vertical() { + assumeTelevision(); + // Vertical expanded PiP. + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true); + + assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.RIGHT, + Gravity.CENTER_VERTICAL | Gravity.RIGHT); + assertGravityAfterExpansion(Gravity.TOP | Gravity.RIGHT, + Gravity.CENTER_VERTICAL | Gravity.RIGHT); + assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.LEFT, + Gravity.CENTER_VERTICAL | Gravity.LEFT); + assertGravityAfterExpansion(Gravity.TOP | Gravity.LEFT, + Gravity.CENTER_VERTICAL | Gravity.LEFT); + } + + @Test + public void updateGravity_expand_horizontal() { + assumeTelevision(); + // Horizontal expanded PiP. + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true); + + assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.RIGHT, + Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL); + assertGravityAfterExpansion(Gravity.TOP | Gravity.RIGHT, + Gravity.TOP | Gravity.CENTER_HORIZONTAL); + assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.LEFT, + Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL); + assertGravityAfterExpansion(Gravity.TOP | Gravity.LEFT, + Gravity.TOP | Gravity.CENTER_HORIZONTAL); + } + + @Test + public void updateGravity_collapse() { + assumeTelevision(); + // Vertical expansion + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true); + assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.RIGHT, + Gravity.BOTTOM | Gravity.RIGHT); + assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.LEFT, + Gravity.BOTTOM | Gravity.LEFT); + + // Horizontal expansion + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true); + assertGravityAfterCollapse(Gravity.TOP | Gravity.CENTER_HORIZONTAL, + Gravity.TOP | Gravity.RIGHT); + assertGravityAfterCollapse(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, + Gravity.BOTTOM | Gravity.RIGHT); + } + + @Test + public void updateGravity_collapse_RTL() { + assumeTelevision(); + setRTL(true); + + // Horizontal expansion + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true); + assertGravityAfterCollapse(Gravity.TOP | Gravity.CENTER_HORIZONTAL, + Gravity.TOP | Gravity.LEFT); + assertGravityAfterCollapse(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, + Gravity.BOTTOM | Gravity.LEFT); + } + + @Test + public void updateGravity_expand_collapse() { + assumeTelevision(); + // Vertical expanded PiP. + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true); + + assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.RIGHT); + assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.LEFT); + assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.LEFT); + assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.RIGHT); + + // Horizontal expanded PiP. + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true); + + assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.RIGHT); + assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.LEFT); + assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.LEFT); + assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.RIGHT); + } + + @Test + public void updateGravity_expand_move_collapse() { + assumeTelevision(); + // Vertical expanded PiP. + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true); + expandMoveCollapseCheck(Gravity.TOP | Gravity.RIGHT, KEYCODE_DPAD_LEFT, + Gravity.TOP | Gravity.LEFT); + + // Horizontal expanded PiP. + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true); + expandMoveCollapseCheck(Gravity.BOTTOM | Gravity.LEFT, KEYCODE_DPAD_UP, + Gravity.TOP | Gravity.LEFT); + } + + private void expandMoveCollapseCheck(int gravityFrom, int keycode, int gravityTo) { + // Expand + mTvPipBoundsState.setTvPipExpanded(false); + mTvPipBoundsState.setTvPipGravity(gravityFrom); + mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true); + // Move + mTvPipBoundsAlgorithm.updateGravity(keycode); + // Collapse + mTvPipBoundsState.setTvPipExpanded(true); + mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false); + + checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo); + } + + private void moveAndCheckGravity(int keycode, int gravityEnd, boolean expectChange) { + assertEquals(expectChange, mTvPipBoundsAlgorithm.updateGravity(keycode)); + checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityEnd); + } + + @Test + public void updateGravity_move_regular_valid() { + assumeTelevision(); + mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.RIGHT); + // clockwise + moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.LEFT, true); + moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.LEFT, true); + moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.TOP | Gravity.RIGHT, true); + moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.RIGHT, true); + // anti-clockwise + moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.RIGHT, true); + moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.TOP | Gravity.LEFT, true); + moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.LEFT, true); + moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.BOTTOM | Gravity.RIGHT, true); + } + + @Test + public void updateGravity_move_expanded_valid() { + assumeTelevision(); + mTvPipBoundsState.setTvPipExpanded(true); + + // Vertical expanded PiP. + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true); + mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); + moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, true); + moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, true); + + // Horizontal expanded PiP. + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true); + mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL); + moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, true); + moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, true); + } + + @Test + public void updateGravity_move_regular_invalid() { + assumeTelevision(); + int gravity = Gravity.BOTTOM | Gravity.RIGHT; + mTvPipBoundsState.setTvPipGravity(gravity); + moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false); + moveAndCheckGravity(KEYCODE_DPAD_RIGHT, gravity, false); + + gravity = Gravity.BOTTOM | Gravity.LEFT; + mTvPipBoundsState.setTvPipGravity(gravity); + moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false); + moveAndCheckGravity(KEYCODE_DPAD_LEFT, gravity, false); + + gravity = Gravity.TOP | Gravity.LEFT; + mTvPipBoundsState.setTvPipGravity(gravity); + moveAndCheckGravity(KEYCODE_DPAD_UP, gravity, false); + moveAndCheckGravity(KEYCODE_DPAD_LEFT, gravity, false); + + gravity = Gravity.TOP | Gravity.RIGHT; + mTvPipBoundsState.setTvPipGravity(gravity); + moveAndCheckGravity(KEYCODE_DPAD_UP, gravity, false); + moveAndCheckGravity(KEYCODE_DPAD_RIGHT, gravity, false); + } + + @Test + public void updateGravity_move_expanded_invalid() { + assumeTelevision(); + mTvPipBoundsState.setTvPipExpanded(true); + + // Vertical expanded PiP. + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true); + mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); + moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false); + moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false); + moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false); + + mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); + moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, false); + moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.LEFT, false); + moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.CENTER_VERTICAL | Gravity.LEFT, false); + + // Horizontal expanded PiP. + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true); + mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL); + moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false); + moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false); + moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false); + + mTvPipBoundsState.setTvPipGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); + moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false); + moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false); + moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false); + } + + @Test + public void previousCollapsedGravity_defaultValue() { + assumeTelevision(); + assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(), + mTvPipBoundsState.getDefaultGravity()); + setRTL(true); + assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(), + mTvPipBoundsState.getDefaultGravity()); + } + + @Test + public void previousCollapsedGravity_changes_on_RTL() { + assumeTelevision(); + mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.TOP | Gravity.LEFT); + setRTL(true); + assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(), + Gravity.TOP | Gravity.RIGHT); + setRTL(false); + assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(), + Gravity.TOP | Gravity.LEFT); + + mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.BOTTOM | Gravity.RIGHT); + setRTL(true); + assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(), + Gravity.BOTTOM | Gravity.LEFT); + setRTL(false); + assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(), + Gravity.BOTTOM | Gravity.RIGHT); + } + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt index 0fcc5cf384c9..aedf65ddc269 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt @@ -21,23 +21,25 @@ import android.graphics.Rect import android.testing.AndroidTestingRunner import android.util.Size import android.view.Gravity -import org.junit.runner.RunWith -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE +import com.android.wm.shell.ShellTestCase import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement -import org.junit.Before -import org.junit.Test import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertNull import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) -class TvPipKeepClearAlgorithmTest { +class TvPipKeepClearAlgorithmTest : ShellTestCase() { private val DEFAULT_PIP_SIZE = Size(384, 216) - private val EXPANDED_WIDE_PIP_SIZE = Size(384*2, 216) + private val EXPANDED_WIDE_PIP_SIZE = Size(384 * 2, 216) + private val EXPANDED_TALL_PIP_SIZE = Size(384, 216 * 4) private val DASHBOARD_WIDTH = 484 private val BOTTOM_SHEET_HEIGHT = 524 private val STASH_OFFSET = 64 @@ -54,6 +56,9 @@ class TvPipKeepClearAlgorithmTest { @Before fun setup() { + if (!isTelevision) { + return + } movementBounds = Rect(0, 0, SCREEN_SIZE.width, SCREEN_SIZE.height) movementBounds.inset(SCREEN_EDGE_INSET, SCREEN_EDGE_INSET) @@ -73,72 +78,84 @@ class TvPipKeepClearAlgorithmTest { @Test fun testAnchorPosition_BottomRight() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT testAnchorPosition() } @Test fun testAnchorPosition_TopRight() { + assumeTelevision() gravity = Gravity.TOP or Gravity.RIGHT testAnchorPosition() } @Test fun testAnchorPosition_TopLeft() { + assumeTelevision() gravity = Gravity.TOP or Gravity.LEFT testAnchorPosition() } @Test fun testAnchorPosition_BottomLeft() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.LEFT testAnchorPosition() } @Test fun testAnchorPosition_Right() { + assumeTelevision() gravity = Gravity.RIGHT testAnchorPosition() } @Test fun testAnchorPosition_Left() { + assumeTelevision() gravity = Gravity.LEFT testAnchorPosition() } @Test fun testAnchorPosition_Top() { + assumeTelevision() gravity = Gravity.TOP testAnchorPosition() } @Test fun testAnchorPosition_Bottom() { + assumeTelevision() gravity = Gravity.BOTTOM testAnchorPosition() } @Test fun testAnchorPosition_TopCenterHorizontal() { + assumeTelevision() gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL testAnchorPosition() } @Test fun testAnchorPosition_BottomCenterHorizontal() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL testAnchorPosition() } @Test fun testAnchorPosition_RightCenterVertical() { + assumeTelevision() gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL testAnchorPosition() } @Test fun testAnchorPosition_LeftCenterVertical() { + assumeTelevision() gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL testAnchorPosition() } @@ -152,6 +169,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_AnchorBottomRight_KeepClearNotObstructing_StayAtAnchor() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.LEFT) @@ -166,6 +184,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_AnchorBottomRight_UnrestrictedRightSidebar_PushedLeft() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) @@ -180,6 +199,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_AnchorTopRight_UnrestrictedRightSidebar_PushedLeft() { + assumeTelevision() gravity = Gravity.TOP or Gravity.RIGHT val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) @@ -194,6 +214,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_AnchorBottomLeft_UnrestrictedRightSidebar_StayAtAnchor() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.LEFT val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) @@ -208,6 +229,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_AnchorBottom_UnrestrictedRightSidebar_StayAtAnchor() { + assumeTelevision() gravity = Gravity.BOTTOM val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) @@ -222,6 +244,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun testExpanded_AnchorBottom_UnrestrictedRightSidebar_StayAtAnchor() { + assumeTelevision() pipSize = EXPANDED_WIDE_PIP_SIZE gravity = Gravity.BOTTOM @@ -237,6 +260,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_AnchorBottomRight_RestrictedSmallBottomBar_PushedUp() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT val bottomBar = makeBottomBar(96) @@ -252,6 +276,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_AnchorBottomRight_RestrictedBottomSheet_StashDownAtAnchor() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) @@ -269,6 +294,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_AnchorBottomRight_UnrestrictedBottomSheet_PushUp() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) @@ -284,6 +310,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_AnchorBottomRight_UnrestrictedBottomSheet_RestrictedSidebar_StashAboveBottomSheet() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) @@ -309,6 +336,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_AnchorBottomRight_UnrestrictedBottomSheet_UnrestrictedSidebar_PushUpLeft() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) @@ -331,6 +359,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_Stashed_UnstashBoundsBecomeUnobstructed_Unstashes() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) @@ -361,6 +390,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_Stashed_UnstashBoundsStaysObstructed_DoesNotTriggerStash() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) @@ -392,6 +422,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_Stashed_UnstashBoundsObstructionChanges_UnstashTimeExtended() { + assumeTelevision() gravity = Gravity.BOTTOM or Gravity.RIGHT val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) @@ -431,6 +462,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_ExpandedPiPHeightExceedsMovementBounds_AtAnchor() { + assumeTelevision() gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL pipSize = Size(DEFAULT_PIP_SIZE.width, SCREEN_SIZE.height) testAnchorPosition() @@ -438,6 +470,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_ExpandedPiPHeightExceedsMovementBounds_BottomBar_StashedUp() { + assumeTelevision() gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL pipSize = Size(DEFAULT_PIP_SIZE.width, SCREEN_SIZE.height) val bottomBar = makeBottomBar(96) @@ -453,6 +486,7 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_PipInsets() { + assumeTelevision() val insets = Insets.of(-1, -2, -3, -4) algorithm.setPipPermanentDecorInsets(insets) @@ -485,6 +519,41 @@ class TvPipKeepClearAlgorithmTest { testAnchorPositionWithInsets(insets) } + @Test + fun test_AnchorRightExpandedPiP_UnrestrictedRightSidebar_PushedLeft() { + assumeTelevision() + pipSize = EXPANDED_TALL_PIP_SIZE + gravity = Gravity.RIGHT + + val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) + unrestrictedAreas.add(sidebar) + + val expectedBounds = anchorBoundsOffsetBy(SCREEN_EDGE_INSET - sidebar.width() - PADDING, 0) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_AnchorRightExpandedPiP_RestrictedRightSidebar_StashedRight() { + assumeTelevision() + assumeTelevision() + pipSize = EXPANDED_TALL_PIP_SIZE + gravity = Gravity.RIGHT + + val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) + restrictedAreas.add(sidebar) + + val expectedBounds = getExpectedAnchorBounds() + expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertEquals(STASH_TYPE_RIGHT, placement.stashType) + assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds) + } + private fun testAnchorPositionWithInsets(insets: Insets) { var pipRect = Rect(0, 0, pipSize.width, pipSize.height) pipRect.inset(insets) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index e5ae2962e6e4..11fda8bf7bbc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -249,7 +249,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay( any() /* window */, any() /* attrs */, anyInt() /* viewVisibility */, anyInt() /* displayId */, - any() /* requestedVisibility */, any() /* outInputChannel */, + anyInt() /* requestedVisibleTypes */, any() /* outInputChannel */, any() /* outInsetsState */, any() /* outActiveControls */, any() /* outAttachedFrame */, any() /* outSizeCompatScale */); TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java deleted file mode 100644 index 3de50bb60470..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (C) 2017 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.startingsurface; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; - -import android.app.ActivityManager.TaskDescription; -import android.content.ComponentName; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorSpace; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.HardwareBuffer; -import android.view.InsetsState; -import android.view.Surface; -import android.view.SurfaceControl; -import android.window.TaskSnapshot; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.TestShellExecutor; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Test class for {@link TaskSnapshotWindow}. - * - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class TaskSnapshotWindowTest extends ShellTestCase { - - private TaskSnapshotWindow mWindow; - - private void setupSurface(int width, int height) { - setupSurface(width, height, new Rect(), 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, width, height)); - } - - private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis, - int windowFlags, Rect taskBounds) { - // Previously when constructing TaskSnapshots for this test, scale was 1.0f, so to mimic - // this behavior set the taskSize to be the same as the taskBounds width and height. The - // taskBounds passed here are assumed to be the same task bounds as when the snapshot was - // taken. We assume there is no aspect ratio mismatch between the screenshot and the - // taskBounds - assertEquals(width, taskBounds.width()); - assertEquals(height, taskBounds.height()); - Point taskSize = new Point(taskBounds.width(), taskBounds.height()); - - final TaskSnapshot snapshot = createTaskSnapshot(width, height, taskSize, contentInsets); - mWindow = new TaskSnapshotWindow(new SurfaceControl(), snapshot, "Test", - createTaskDescription(Color.WHITE, Color.RED, Color.BLUE), - 0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */, - taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD, - new InsetsState(), null /* clearWindow */, new TestShellExecutor()); - } - - private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize, - Rect contentInsets) { - final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, - 1, HardwareBuffer.USAGE_CPU_READ_RARELY); - return new TaskSnapshot( - System.currentTimeMillis(), - new ComponentName("", ""), buffer, - ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, - Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */, - false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, - 0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */); - } - - private static TaskDescription createTaskDescription(int background, int statusBar, - int navigationBar) { - final TaskDescription td = new TaskDescription(); - td.setBackgroundColor(background); - td.setStatusBarColor(statusBar); - td.setNavigationBarColor(navigationBar); - return td; - } - - @Test - public void fillEmptyBackground_fillHorizontally() { - setupSurface(200, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(200); - when(mockCanvas.getHeight()).thenReturn(100); - mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200)); - verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); - } - - @Test - public void fillEmptyBackground_fillVertically() { - setupSurface(100, 200); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(200); - mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100)); - verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any()); - } - - @Test - public void fillEmptyBackground_fillBoth() { - setupSurface(200, 200); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(200); - when(mockCanvas.getHeight()).thenReturn(200); - mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); - verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); - verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any()); - } - - @Test - public void fillEmptyBackground_dontFill_sameSize() { - setupSurface(100, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void fillEmptyBackground_dontFill_bitmapLarger() { - setupSurface(100, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200)); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void testCalculateSnapshotCrop() { - setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 100, 90), mWindow.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotCrop_taskNotOnTop() { - setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 150)); - assertEquals(new Rect(0, 10, 100, 90), mWindow.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotCrop_navBarLeft() { - setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(10, 0, 100, 100), mWindow.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotCrop_navBarRight() { - setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 90, 100), mWindow.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotCrop_waterfall() { - setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(5, 0, 95, 90), mWindow.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotFrame() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 0, 10); - mWindow.setFrames(new Rect(0, 0, 100, 100), insets); - assertEquals(new Rect(0, 0, 100, 80), - mWindow.calculateSnapshotFrame(new Rect(0, 10, 100, 90))); - } - - @Test - public void testCalculateSnapshotFrame_navBarLeft() { - setupSurface(100, 100); - final Rect insets = new Rect(10, 10, 0, 0); - mWindow.setFrames(new Rect(0, 0, 100, 100), insets); - assertEquals(new Rect(10, 0, 100, 90), - mWindow.calculateSnapshotFrame(new Rect(10, 10, 100, 100))); - } - - @Test - public void testCalculateSnapshotFrame_waterfall() { - setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100)); - final Rect insets = new Rect(0, 10, 0, 10); - mWindow.setFrames(new Rect(5, 0, 95, 100), insets); - assertEquals(new Rect(0, 0, 90, 90), - mWindow.calculateSnapshotFrame(new Rect(5, 0, 95, 90))); - } - - @Test - public void testDrawStatusBarBackground() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 10, 0); - mWindow.setFrames(new Rect(0, 0, 100, 100), insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100)); - verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); - } - - @Test - public void testDrawStatusBarBackground_nullFrame() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 10, 0); - mWindow.setFrames(new Rect(0, 0, 100, 100), insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mWindow.drawStatusBarBackground(mockCanvas, null); - verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); - } - - @Test - public void testDrawStatusBarBackground_nope() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 10, 0); - mWindow.setFrames(new Rect(0, 0, 100, 100), insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100)); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void testDrawNavigationBarBackground() { - final Rect insets = new Rect(0, 10, 0, 10); - setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, 100, 100)); - mWindow.setFrames(new Rect(0, 0, 100, 100), insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mWindow.drawNavigationBarBackground(mockCanvas); - verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any()); - } - - @Test - public void testDrawNavigationBarBackground_left() { - final Rect insets = new Rect(10, 10, 0, 0); - setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, 100, 100)); - mWindow.setFrames(new Rect(0, 0, 100, 100), insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mWindow.drawNavigationBarBackground(mockCanvas); - verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any()); - } - - @Test - public void testDrawNavigationBarBackground_right() { - final Rect insets = new Rect(0, 10, 10, 0); - setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, 100, 100)); - mWindow.setFrames(new Rect(0, 0, 100, 100), insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mWindow.drawNavigationBarBackground(mockCanvas); - verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any()); - } -} diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index c80fb188e70f..b1f327c94f8e 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -33,6 +33,7 @@ license { cc_defaults { name: "libandroidfw_defaults", + cpp_std: "gnu++2b", cflags: [ "-Werror", "-Wunreachable-code", @@ -60,6 +61,7 @@ cc_library { "AssetManager2.cpp", "AssetsProvider.cpp", "AttributeResolution.cpp", + "BigBuffer.cpp", "ChunkIterator.cpp", "ConfigDescription.cpp", "Idmap.cpp", @@ -69,9 +71,11 @@ cc_library { "misc.cpp", "ObbFile.cpp", "PosixUtils.cpp", + "ResourceTimer.cpp", "ResourceTypes.cpp", "ResourceUtils.cpp", "StreamingZipInflater.cpp", + "StringPool.cpp", "TypeWrappers.cpp", "Util.cpp", "ZipFileRO.cpp", @@ -161,6 +165,7 @@ cc_test { "tests/AssetManager2_test.cpp", "tests/AttributeFinder_test.cpp", "tests/AttributeResolution_test.cpp", + "tests/BigBuffer_test.cpp", "tests/ByteBucketArray_test.cpp", "tests/Config_test.cpp", "tests/ConfigDescription_test.cpp", @@ -169,10 +174,12 @@ cc_test { "tests/Idmap_test.cpp", "tests/LoadedArsc_test.cpp", "tests/Locale_test.cpp", + "tests/ResourceTimer_test.cpp", "tests/ResourceUtils_test.cpp", "tests/ResTable_test.cpp", "tests/Split_test.cpp", "tests/StringPiece_test.cpp", + "tests/StringPool_test.cpp", "tests/Theme_test.cpp", "tests/TypeWrappers_test.cpp", "tests/ZipUtils_test.cpp", @@ -204,6 +211,8 @@ cc_test { "tests/data/**/*.apk", "tests/data/**/*.arsc", "tests/data/**/*.idmap", + ":FrameworkResourcesSparseTestApp", + ":FrameworkResourcesNotSparseTestApp", ], test_suites: ["device-tests"], } diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 2beb33abe782..15aaae25f754 100755..100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -18,6 +18,7 @@ #include "android-base/errors.h" #include "android-base/logging.h" +#include "android-base/utf8.h" namespace android { @@ -83,15 +84,16 @@ std::unique_ptr<ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path, return {}; } + std::string overlay_path(loaded_idmap->OverlayApkPath()); + auto fd = unique_fd(base::utf8::open(overlay_path.c_str(), O_RDONLY | O_CLOEXEC)); std::unique_ptr<AssetsProvider> overlay_assets; - const std::string overlay_path(loaded_idmap->OverlayApkPath()); - if (IsFabricatedOverlay(overlay_path)) { + if (IsFabricatedOverlay(fd)) { // Fabricated overlays do not contain resource definitions. All of the overlay resource values // are defined inline in the idmap. - overlay_assets = EmptyAssetsProvider::Create(overlay_path); + overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path)); } else { // The overlay should be an APK. - overlay_assets = ZipAssetsProvider::Create(overlay_path, flags); + overlay_assets = ZipAssetsProvider::Create(std::move(overlay_path), flags, std::move(fd)); } if (overlay_assets == nullptr) { return {}; @@ -141,6 +143,9 @@ std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_ return {}; } loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags); + } else if (loaded_idmap != nullptr && + IsFabricatedOverlay(std::string(loaded_idmap->OverlayApkPath()))) { + loaded_arsc = LoadedArsc::Load(loaded_idmap.get()); } else { loaded_arsc = LoadedArsc::CreateEmpty(); } diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 400829e15364..68f5e4a88c7e 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -22,6 +22,7 @@ #include <iterator> #include <map> #include <set> +#include <span> #include "android-base/logging.h" #include "android-base/stringprintf.h" @@ -43,28 +44,19 @@ namespace { using EntryValue = std::variant<Res_value, incfs::verified_map_ptr<ResTable_map_entry>>; +/* NOTE: table_entry has been verified in LoadedPackage::GetEntryFromOffset(), + * and so access to ->value() and ->map_entry() are safe here + */ base::expected<EntryValue, IOError> GetEntryValue( incfs::verified_map_ptr<ResTable_entry> table_entry) { - const uint16_t entry_size = dtohs(table_entry->size); + const uint16_t entry_size = table_entry->size(); // Check if the entry represents a bag value. - if (entry_size >= sizeof(ResTable_map_entry) && - (dtohs(table_entry->flags) & ResTable_entry::FLAG_COMPLEX)) { - const auto map_entry = table_entry.convert<ResTable_map_entry>(); - if (!map_entry) { - return base::unexpected(IOError::PAGES_MISSING); - } - return map_entry.verified(); + if (entry_size >= sizeof(ResTable_map_entry) && table_entry->is_complex()) { + return table_entry.convert<ResTable_map_entry>().verified(); } - // The entry represents a non-bag value. - const auto entry_value = table_entry.offset(entry_size).convert<Res_value>(); - if (!entry_value) { - return base::unexpected(IOError::PAGES_MISSING); - } - Res_value value; - value.copyFrom_dtoh(entry_value.value()); - return value; + return table_entry->value(); } } // namespace @@ -120,7 +112,7 @@ void AssetManager2::BuildDynamicRefTable() { // A mapping from path of apk assets that could be target packages of overlays to the runtime // package id of its first loaded package. Overlays currently can only override resources in the // first package in the target resource table. - std::unordered_map<std::string, uint8_t> target_assets_package_ids; + std::unordered_map<std::string_view, uint8_t> target_assets_package_ids; // Overlay resources are not directly referenced by an application so their resource ids // can change throughout the application's lifetime. Assign overlay package ids last. @@ -143,7 +135,7 @@ void AssetManager2::BuildDynamicRefTable() { if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) { // The target package must precede the overlay package in the apk assets paths in order // to take effect. - auto iter = target_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath())); + auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath()); if (iter == target_assets_package_ids.end()) { LOG(INFO) << "failed to find target package for overlay " << loaded_idmap->OverlayApkPath(); @@ -188,7 +180,7 @@ void AssetManager2::BuildDynamicRefTable() { if (overlay_ref_table != nullptr) { // If this package is from an overlay, use a dynamic reference table that can rewrite // overlay resource ids to their corresponding target resource ids. - new_group.dynamic_ref_table = overlay_ref_table; + new_group.dynamic_ref_table = std::move(overlay_ref_table); } DynamicRefTable* ref_table = new_group.dynamic_ref_table.get(); @@ -196,9 +188,9 @@ void AssetManager2::BuildDynamicRefTable() { ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f; } - // Add the package and to the set of packages with the same ID. + // Add the package to the set of packages with the same ID. PackageGroup* package_group = &package_groups_[idx]; - package_group->packages_.push_back(ConfiguredPackage{package.get(), {}}); + package_group->packages_.emplace_back().loaded_package_ = package.get(); package_group->cookies_.push_back(apk_assets_cookies[apk_assets]); // Add the package name -> build time ID mappings. @@ -210,30 +202,39 @@ void AssetManager2::BuildDynamicRefTable() { if (auto apk_assets_path = apk_assets->GetPath()) { // Overlay target ApkAssets must have been created using path based load apis. - target_assets_package_ids.insert(std::make_pair(std::string(*apk_assets_path), package_id)); + target_assets_package_ids.emplace(*apk_assets_path, package_id); } } } // Now assign the runtime IDs so that we have a build-time to runtime ID map. - const auto package_groups_end = package_groups_.end(); - for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) { - const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName(); - for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) { - iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()), - iter->dynamic_ref_table->mAssignedPackageId); - - // Add the alias resources to the dynamic reference table of every package group. Since - // staging aliases can only be defined by the framework package (which is not a shared - // library), the compile-time package id of the framework is the same across all packages - // that compile against the framework. - for (const auto& package : iter->packages_) { - for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) { - iter2->dynamic_ref_table->addAlias(entry.first, entry.second); - } - } + DynamicRefTable::AliasMap aliases; + for (const auto& group : package_groups_) { + const std::string& package_name = group.packages_[0].loaded_package_->GetPackageName(); + const auto name_16 = String16(package_name.c_str(), package_name.size()); + for (auto&& inner_group : package_groups_) { + inner_group.dynamic_ref_table->addMapping(name_16, + group.dynamic_ref_table->mAssignedPackageId); + } + + for (const auto& package : group.packages_) { + const auto& package_aliases = package.loaded_package_->GetAliasResourceIdMap(); + aliases.insert(aliases.end(), package_aliases.begin(), package_aliases.end()); } } + + if (!aliases.empty()) { + std::sort(aliases.begin(), aliases.end(), [](auto&& l, auto&& r) { return l.first < r.first; }); + + // Add the alias resources to the dynamic reference table of every package group. Since + // staging aliases can only be defined by the framework package (which is not a shared + // library), the compile-time package id of the framework is the same across all packages + // that compile against the framework. + for (auto& group : std::span(package_groups_.data(), package_groups_.size() - 1)) { + group.dynamic_ref_table->setAliases(aliases); + } + package_groups_.back().dynamic_ref_table->setAliases(std::move(aliases)); + } } void AssetManager2::DumpToLog() const { @@ -326,7 +327,7 @@ const std::unordered_map<std::string, std::string>* return &loaded_package->GetOverlayableMap(); } -bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_name, +bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name, std::string* out) const { uint8_t package_id = 0U; for (const auto& apk_assets : apk_assets_) { @@ -373,7 +374,7 @@ bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_ const std::string name = ToFormattedResourceString(*res_name); output.append(base::StringPrintf( "resource='%s' overlayable='%s' actor='%s' policy='0x%08x'\n", - name.c_str(), info->name.c_str(), info->actor.c_str(), info->policy_flags)); + name.c_str(), info->name.data(), info->actor.data(), info->policy_flags)); } } } @@ -501,7 +502,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) con continue; } - auto func = [&](const StringPiece& name, FileType type) { + auto func = [&](StringPiece name, FileType type) { AssetDir::FileInfo info; info.setFileName(String8(name.data(), name.size())); info.setFileType(type); @@ -611,7 +612,21 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( } if (overlay_entry.IsInlineValue()) { // The target resource is overlaid by an inline value not represented by a resource. - result->entry = overlay_entry.GetInlineValue(); + ConfigDescription best_frro_config; + Res_value best_frro_value; + bool frro_found = false; + for( const auto& [config, value] : overlay_entry.GetInlineValue()) { + if ((!frro_found || config.isBetterThan(best_frro_config, desired_config)) + && config.match(*desired_config)) { + frro_found = true; + best_frro_config = config; + best_frro_value = value; + } + } + if (!frro_found) { + continue; + } + result->entry = best_frro_value; result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable(); result->cookie = id_map.cookie; @@ -800,17 +815,12 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( return base::unexpected(std::nullopt); } - auto best_entry_result = LoadedPackage::GetEntryFromOffset(best_type, best_offset); - if (!best_entry_result.has_value()) { - return base::unexpected(best_entry_result.error()); - } - - const incfs::map_ptr<ResTable_entry> best_entry = *best_entry_result; - if (!best_entry) { - return base::unexpected(IOError::PAGES_MISSING); + auto best_entry_verified = LoadedPackage::GetEntryFromOffset(best_type, best_offset); + if (!best_entry_verified.has_value()) { + return base::unexpected(best_entry_verified.error()); } - const auto entry = GetEntryValue(best_entry.verified()); + const auto entry = GetEntryValue(*best_entry_verified); if (!entry.has_value()) { return base::unexpected(entry.error()); } @@ -823,7 +833,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( .package_name = &best_package->GetPackageName(), .type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1), .entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(), - best_entry->key.index), + (*best_entry_verified)->key()), .dynamic_ref_table = package_group.dynamic_ref_table.get(), }; } @@ -1054,7 +1064,7 @@ base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::ResolveBag( base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag(uint32_t resid) const { std::vector<uint32_t> found_resids; const auto bag = GetBag(resid, found_resids); - cached_bag_resid_stacks_.emplace(resid, found_resids); + cached_bag_resid_stacks_.emplace(resid, std::move(found_resids)); return bag; } @@ -1271,7 +1281,7 @@ base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag( return result; } -static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) { +static bool Utf8ToUtf16(StringPiece str, std::u16string* out) { ssize_t len = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size(), false); if (len < 0) { @@ -1346,22 +1356,22 @@ base::expected<uint32_t, NullOrIOError> AssetManager2::GetResourceId( void AssetManager2::RebuildFilterList() { for (PackageGroup& group : package_groups_) { - for (ConfiguredPackage& impl : group.packages_) { - // Destroy it. - impl.filtered_configs_.~ByteBucketArray(); - - // Re-create it. - new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>(); - + for (ConfiguredPackage& package : group.packages_) { + package.filtered_configs_.forEachItem([](auto, auto& fcg) { fcg.type_entries.clear(); }); // Create the filters here. - impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) { - FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_id - 1); + package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) { + FilteredConfigGroup* group = nullptr; for (const auto& type_entry : type_spec.type_entries) { if (type_entry.config.match(configuration_)) { - group.type_entries.push_back(&type_entry); + if (!group) { + group = &package.filtered_configs_.editItemAt(type_id - 1); + } + group->type_entries.push_back(&type_entry); } } }); + package.filtered_configs_.trimBuckets( + [](const auto& fcg) { return fcg.type_entries.empty(); }); } } } @@ -1402,30 +1412,34 @@ uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const std::unique_ptr<Theme> AssetManager2::NewTheme() { constexpr size_t kInitialReserveSize = 32; auto theme = std::unique_ptr<Theme>(new Theme(this)); + theme->keys_.reserve(kInitialReserveSize); theme->entries_.reserve(kInitialReserveSize); return theme; } +void AssetManager2::ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func, + package_property_t excluded_property_flags) const { + for (const PackageGroup& package_group : package_groups_) { + const auto loaded_package = package_group.packages_.front().loaded_package_; + if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U + && !func(loaded_package->GetPackageName(), + package_group.dynamic_ref_table->mAssignedPackageId)) { + return; + } + } +} + Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) { } Theme::~Theme() = default; struct Theme::Entry { - uint32_t attr_res_id; ApkAssetsCookie cookie; uint32_t type_spec_flags; Res_value value; }; -namespace { -struct ThemeEntryKeyComparer { - bool operator() (const Theme::Entry& entry, uint32_t attr_res_id) const noexcept { - return entry.attr_res_id < attr_res_id; - } -}; -} // namespace - base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) { ATRACE_NAME("Theme::ApplyStyle"); @@ -1454,19 +1468,20 @@ base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, continue; } - Theme::Entry new_entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value}; - auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), attr_res_id, - ThemeEntryKeyComparer{}); - if (entry_it != entries_.end() && entry_it->attr_res_id == attr_res_id) { + const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id); + const auto entry_it = entries_.begin() + (key_it - keys_.begin()); + if (key_it != keys_.end() && *key_it == attr_res_id) { if (is_undefined) { // DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is - /// true. + // true. + keys_.erase(key_it); entries_.erase(entry_it); } else if (force) { - *entry_it = new_entry; + *entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value}; } } else { - entries_.insert(entry_it, new_entry); + keys_.insert(key_it, attr_res_id); + entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value}); } } return {}; @@ -1477,6 +1492,7 @@ void Theme::Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t* ATRACE_NAME("Theme::Rebase"); // Reset the entries without changing the vector capacity to prevent reallocations during // ApplyStyle. + keys_.clear(); entries_.clear(); asset_manager_ = am; for (size_t i = 0; i < style_count; i++) { @@ -1485,16 +1501,14 @@ void Theme::Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t* } std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) const { - constexpr const uint32_t kMaxIterations = 20; uint32_t type_spec_flags = 0u; for (uint32_t i = 0; i <= kMaxIterations; i++) { - auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), resid, - ThemeEntryKeyComparer{}); - if (entry_it == entries_.end() || entry_it->attr_res_id != resid) { + const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), resid); + if (key_it == keys_.end() || *key_it != resid) { return std::nullopt; } - + const auto entry_it = entries_.begin() + (key_it - keys_.begin()); type_spec_flags |= entry_it->type_spec_flags; if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) { resid = entry_it->value.data; @@ -1528,6 +1542,7 @@ base::expected<std::monostate, NullOrIOError> Theme::ResolveAttributeReference( } void Theme::Clear() { + keys_.clear(); entries_.clear(); } @@ -1539,18 +1554,19 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { type_spec_flags_ = source.type_spec_flags_; if (asset_manager_ == source.asset_manager_) { + keys_ = source.keys_; entries_ = source.entries_; } else { - std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies; - typedef std::map<int, int> SourceToDestinationRuntimePackageMap; - std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map; + std::unordered_map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies; + using SourceToDestinationRuntimePackageMap = std::unordered_map<int, int>; + std::unordered_map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map; // Determine which ApkAssets are loaded in both theme AssetManagers. - const auto src_assets = source.asset_manager_->GetApkAssets(); + const auto& src_assets = source.asset_manager_->GetApkAssets(); for (size_t i = 0; i < src_assets.size(); i++) { const ApkAssets* src_asset = src_assets[i]; - const auto dest_assets = asset_manager_->GetApkAssets(); + const auto& dest_assets = asset_manager_->GetApkAssets(); for (size_t j = 0; j < dest_assets.size(); j++) { const ApkAssets* dest_asset = dest_assets[j]; if (src_asset != dest_asset) { @@ -1571,15 +1587,17 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { } src_to_dest_asset_cookies.insert(std::make_pair(i, j)); - src_asset_cookie_id_map.insert(std::make_pair(i, package_map)); + src_asset_cookie_id_map.insert(std::make_pair(i, std::move(package_map))); break; } } // Reset the data in the destination theme. + keys_.clear(); entries_.clear(); - for (const auto& entry : source.entries_) { + for (size_t i = 0, size = source.entries_.size(); i != size; ++i) { + const auto& entry = source.entries_[i]; bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE || entry.value.dataType == Res_value::TYPE_REFERENCE || entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE @@ -1619,13 +1637,15 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { } } + const auto source_res_id = source.keys_[i]; + // The package id of the attribute needs to be rewritten to the package id of the // attribute in the destination. - int attribute_dest_package_id = get_package_id(entry.attr_res_id); + int attribute_dest_package_id = get_package_id(source_res_id); if (attribute_dest_package_id != 0x01) { // Find the cookie of the attribute resource id in the source AssetManager base::expected<FindEntryResult, NullOrIOError> attribute_entry_result = - source.asset_manager_->FindEntry(entry.attr_res_id, 0 /* density_override */ , + source.asset_manager_->FindEntry(source_res_id, 0 /* density_override */ , true /* stop_at_first_match */, true /* ignore_configuration */); if (UNLIKELY(IsIOError(attribute_entry_result))) { @@ -1649,16 +1669,15 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { attribute_dest_package_id = attribute_dest_package->second; } - auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(entry.attr_res_id), - get_entry_id(entry.attr_res_id)); - Theme::Entry new_entry{dest_attr_id, data_dest_cookie, entry.type_spec_flags, - Res_value{.dataType = entry.value.dataType, - .data = attribute_data}}; - + auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(source_res_id), + get_entry_id(source_res_id)); + const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), dest_attr_id); + const auto entry_it = entries_.begin() + (key_it - keys_.begin()); // Since the entries were cleared, the attribute resource id has yet been mapped to any value. - auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), dest_attr_id, - ThemeEntryKeyComparer{}); - entries_.insert(entry_it, new_entry); + keys_.insert(key_it, dest_attr_id); + entries_.insert(entry_it, Entry{data_dest_cookie, entry.type_spec_flags, + Res_value{.dataType = entry.value.dataType, + .data = attribute_data}}); } } return {}; @@ -1666,9 +1685,11 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { void Theme::Dump() const { LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_); - for (auto& entry : entries_) { + for (size_t i = 0, size = keys_.size(); i != size; ++i) { + auto res_id = keys_[i]; + const auto& entry = entries_[i]; LOG(INFO) << base::StringPrintf(" entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)", - entry.attr_res_id, entry.value.data, entry.value.dataType, + res_id, entry.value.data, entry.value.dataType, entry.cookie); } } diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp index bce34d37c90b..2d3c06506a1f 100644 --- a/libs/androidfw/AssetsProvider.cpp +++ b/libs/androidfw/AssetsProvider.cpp @@ -73,9 +73,6 @@ std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFd(base::unique_fd fd, (path != nullptr) ? base::unique_fd(-1) : std::move(fd)); } -ZipAssetsProvider::PathOrDebugName::PathOrDebugName(std::string&& value, bool is_path) - : value_(std::forward<std::string>(value)), is_path_(is_path) {} - const std::string* ZipAssetsProvider::PathOrDebugName::GetPath() const { return is_path_ ? &value_ : nullptr; } @@ -84,34 +81,42 @@ const std::string& ZipAssetsProvider::PathOrDebugName::GetDebugName() const { return value_; } +void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const { + ::CloseArchive(a); +} + ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path, package_property_t flags, time_t last_mod_time) - : zip_handle_(handle, ::CloseArchive), - name_(std::forward<PathOrDebugName>(path)), + : zip_handle_(handle), + name_(std::move(path)), flags_(flags), last_mod_time_(last_mod_time) {} std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, - package_property_t flags) { + package_property_t flags, + base::unique_fd fd) { + const auto released_fd = fd.ok() ? fd.release() : -1; ZipArchiveHandle handle; - if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) { + if (int32_t result = released_fd < 0 ? OpenArchive(path.c_str(), &handle) + : OpenArchiveFd(released_fd, path.c_str(), &handle)) { LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result); CloseArchive(handle); return {}; } struct stat sb{.st_mtime = -1}; - if (stat(path.c_str(), &sb) < 0) { - // Stat requires execute permissions on all directories path to the file. If the process does - // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will - // always have to return true. - LOG(WARNING) << "Failed to stat file '" << path << "': " - << base::SystemErrorCodeToString(errno); + // Skip all up-to-date checks if the file won't ever change. + if (!isReadonlyFilesystem(path.c_str())) { + if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) { + // Stat requires execute permissions on all directories path to the file. If the process does + // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will + // always have to return true. + PLOG(WARNING) << "Failed to stat file '" << path << "'"; + } } return std::unique_ptr<ZipAssetsProvider>( - new ZipAssetsProvider(handle, PathOrDebugName{std::move(path), - true /* is_path */}, flags, sb.st_mtime)); + new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime)); } std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, @@ -133,17 +138,19 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, } struct stat sb{.st_mtime = -1}; - if (fstat(released_fd, &sb) < 0) { - // Stat requires execute permissions on all directories path to the file. If the process does - // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will - // always have to return true. - LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': " - << base::SystemErrorCodeToString(errno); + // Skip all up-to-date checks if the file won't ever change. + if (!isReadonlyFilesystem(released_fd)) { + if (fstat(released_fd, &sb) < 0) { + // Stat requires execute permissions on all directories path to the file. If the process does + // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will + // always have to return true. + LOG(WARNING) << "Failed to fstat file '" << friendly_name + << "': " << base::SystemErrorCodeToString(errno); + } } - return std::unique_ptr<ZipAssetsProvider>( - new ZipAssetsProvider(handle, PathOrDebugName{std::move(friendly_name), - false /* is_path */}, flags, sb.st_mtime)); + return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider( + handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime)); } std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path, @@ -210,9 +217,9 @@ std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path, return asset; } -bool ZipAssetsProvider::ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) - const { +bool ZipAssetsProvider::ForEachFile( + const std::string& root_path, + base::function_ref<void(StringPiece, FileType)> f) const { std::string root_path_full = root_path; if (root_path_full.back() != '/') { root_path_full += '/'; @@ -238,8 +245,7 @@ bool ZipAssetsProvider::ForEachFile(const std::string& root_path, if (!leaf_file_path.empty()) { auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/'); if (iter != leaf_file_path.end()) { - std::string dir = - leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string(); + std::string dir(leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter))); dirs.insert(std::move(dir)); } else { f(leaf_file_path, kFileTypeRegular); @@ -277,6 +283,9 @@ const std::string& ZipAssetsProvider::GetDebugName() const { } bool ZipAssetsProvider::IsUpToDate() const { + if (last_mod_time_ == -1) { + return true; + } struct stat sb{}; if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) { // If fstat fails on the zip archive, return true so the zip archive the resource system does @@ -287,10 +296,10 @@ bool ZipAssetsProvider::IsUpToDate() const { } DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time) - : dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {} + : dir_(std::move(path)), last_mod_time_(last_mod_time) {} std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) { - struct stat sb{}; + struct stat sb; const int result = stat(path.c_str(), &sb); if (result == -1) { LOG(ERROR) << "Failed to find directory '" << path << "'."; @@ -302,12 +311,13 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st return nullptr; } - if (path[path.size() - 1] != OS_PATH_SEPARATOR) { + if (path.back() != OS_PATH_SEPARATOR) { path += OS_PATH_SEPARATOR; } - return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path), - sb.st_mtime)); + const bool isReadonly = isReadonlyFilesystem(path.c_str()); + return std::unique_ptr<DirectoryAssetsProvider>( + new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime)); } std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path, @@ -324,8 +334,7 @@ std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& bool DirectoryAssetsProvider::ForEachFile( const std::string& /* root_path */, - const std::function<void(const StringPiece&, FileType)>& /* f */) - const { + base::function_ref<void(StringPiece, FileType)> /* f */) const { return true; } @@ -338,7 +347,10 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const { } bool DirectoryAssetsProvider::IsUpToDate() const { - struct stat sb{}; + if (last_mod_time_ == -1) { + return true; + } + struct stat sb; if (stat(dir_.c_str(), &sb) < 0) { // If stat fails on the zip archive, return true so the zip archive the resource system does // attempt to refresh the ApkAsset. @@ -349,8 +361,7 @@ bool DirectoryAssetsProvider::IsUpToDate() const { MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) - : primary_(std::forward<std::unique_ptr<AssetsProvider>>(primary)), - secondary_(std::forward<std::unique_ptr<AssetsProvider>>(secondary)) { + : primary_(std::move(primary)), secondary_(std::move(secondary)) { debug_name_ = primary_->GetDebugName() + " and " + secondary_->GetDebugName(); path_ = (primary_->GetDebugName() != kEmptyDebugString) ? primary_->GetPath() : secondary_->GetPath(); @@ -372,9 +383,9 @@ std::unique_ptr<Asset> MultiAssetsProvider::OpenInternal(const std::string& path return (asset) ? std::move(asset) : secondary_->Open(path, mode, file_exists); } -bool MultiAssetsProvider::ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) - const { +bool MultiAssetsProvider::ForEachFile( + const std::string& root_path, + base::function_ref<void(StringPiece, FileType)> f) const { return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f); } @@ -397,8 +408,8 @@ std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create() { return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider({})); } -std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(const std::string& path) { - return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(path)); +std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(std::string path) { + return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(std::move(path))); } std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */, @@ -412,7 +423,7 @@ std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* p bool EmptyAssetsProvider::ForEachFile( const std::string& /* root_path */, - const std::function<void(const StringPiece&, FileType)>& /* f */) const { + base::function_ref<void(StringPiece, FileType)> /* f */) const { return true; } @@ -435,4 +446,4 @@ bool EmptyAssetsProvider::IsUpToDate() const { return true; } -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/libs/androidfw/BigBuffer.cpp b/libs/androidfw/BigBuffer.cpp new file mode 100644 index 000000000000..bedfc49a1b0d --- /dev/null +++ b/libs/androidfw/BigBuffer.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2015 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. + */ + +#include <androidfw/BigBuffer.h> + +#include <algorithm> +#include <memory> +#include <vector> + +#include "android-base/logging.h" + +namespace android { + +void* BigBuffer::NextBlockImpl(size_t size) { + if (!blocks_.empty()) { + Block& block = blocks_.back(); + if (block.block_size_ - block.size >= size) { + void* out_buffer = block.buffer.get() + block.size; + block.size += size; + size_ += size; + return out_buffer; + } + } + + const size_t actual_size = std::max(block_size_, size); + + Block block = {}; + + // Zero-allocate the block's buffer. + block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actual_size]()); + CHECK(block.buffer); + + block.size = size; + block.block_size_ = actual_size; + + blocks_.push_back(std::move(block)); + size_ += size; + return blocks_.back().buffer.get(); +} + +void* BigBuffer::NextBlock(size_t* out_size) { + if (!blocks_.empty()) { + Block& block = blocks_.back(); + if (block.size != block.block_size_) { + void* out_buffer = block.buffer.get() + block.size; + size_t size = block.block_size_ - block.size; + block.size = block.block_size_; + size_ += size; + *out_size = size; + return out_buffer; + } + } + + // Zero-allocate the block's buffer. + Block block = {}; + block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[block_size_]()); + CHECK(block.buffer); + block.size = block_size_; + block.block_size_ = block_size_; + blocks_.push_back(std::move(block)); + size_ += block_size_; + *out_size = block_size_; + return blocks_.back().buffer.get(); +} + +std::string BigBuffer::to_string() const { + std::string result; + for (const Block& block : blocks_) { + result.append(block.buffer.get(), block.buffer.get() + block.size); + } + return result; +} + +} // namespace android diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp index 19ead9583eb2..93a7d173cb97 100644 --- a/libs/androidfw/ConfigDescription.cpp +++ b/libs/androidfw/ConfigDescription.cpp @@ -637,7 +637,7 @@ static bool parseVersion(const char* name, ResTable_config* out) { return true; } -bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) { +bool ConfigDescription::Parse(StringPiece str, ConfigDescription* out) { std::vector<std::string> parts = util::SplitAndLowercase(str, '-'); ConfigDescription config; diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index efd1f6a25786..89835742c8ff 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -56,6 +56,8 @@ struct Idmap_header { struct Idmap_data_header { uint32_t target_entry_count; uint32_t target_inline_entry_count; + uint32_t target_inline_entry_value_count; + uint32_t configuration_count; uint32_t overlay_entry_count; uint32_t string_pool_index_offset; @@ -68,6 +70,12 @@ struct Idmap_target_entry { struct Idmap_target_entry_inline { uint32_t target_id; + uint32_t start_value_index; + uint32_t value_count; +}; + +struct Idmap_target_entry_inline_value { + uint32_t config_index; Res_value value; }; @@ -138,11 +146,15 @@ status_t OverlayDynamicRefTable::lookupResourceIdNoRewrite(uint32_t* resId) cons IdmapResMap::IdmapResMap(const Idmap_data_header* data_header, const Idmap_target_entry* entries, const Idmap_target_entry_inline* inline_entries, + const Idmap_target_entry_inline_value* inline_entry_values, + const ConfigDescription* configs, uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) : data_header_(data_header), entries_(entries), inline_entries_(inline_entries), + inline_entry_values_(inline_entry_values), + configurations_(configs), target_assigned_package_id_(target_assigned_package_id), overlay_ref_table_(overlay_ref_table) { } @@ -183,7 +195,13 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const { if (inline_entry != end_inline_entry && (0x00FFFFFFU & dtohl(inline_entry->target_id)) == target_res_id) { - return Result(inline_entry->value); + std::map<ConfigDescription, Res_value> values_map; + for (int i = 0; i < inline_entry->value_count; i++) { + const auto& value = inline_entry_values_[inline_entry->start_value_index + i]; + const auto& config = configurations_[value.config_index]; + values_map[config] = value.value; + } + return Result(std::move(values_map)); } return {}; } @@ -232,28 +250,29 @@ std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size } } // namespace -LoadedIdmap::LoadedIdmap(std::string&& idmap_path, - const Idmap_header* header, +LoadedIdmap::LoadedIdmap(std::string&& idmap_path, const Idmap_header* header, const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_target_entry_inline* target_inline_entries, + const Idmap_target_entry_inline_value* inline_entry_values, + const ConfigDescription* configs, const Idmap_overlay_entry* overlay_entries, std::unique_ptr<ResStringPool>&& string_pool, - std::string_view overlay_apk_path, - std::string_view target_apk_path) - : header_(header), - data_header_(data_header), - target_entries_(target_entries), - target_inline_entries_(target_inline_entries), - overlay_entries_(overlay_entries), - string_pool_(std::move(string_pool)), - idmap_path_(std::move(idmap_path)), - overlay_apk_path_(overlay_apk_path), - target_apk_path_(target_apk_path), - idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {} - -std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, - const StringPiece& idmap_data) { + std::string_view overlay_apk_path, std::string_view target_apk_path) + : header_(header), + data_header_(data_header), + target_entries_(target_entries), + target_inline_entries_(target_inline_entries), + inline_entry_values_(inline_entry_values), + configurations_(configs), + overlay_entries_(overlay_entries), + string_pool_(std::move(string_pool)), + idmap_path_(std::move(idmap_path)), + overlay_apk_path_(overlay_apk_path), + target_apk_path_(target_apk_path), + idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {} + +std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) { ATRACE_CALL(); size_t data_size = idmap_data.size(); auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()); @@ -303,6 +322,21 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, if (target_inline_entries == nullptr) { return {}; } + + auto target_inline_entry_values = ReadType<Idmap_target_entry_inline_value>( + &data_ptr, &data_size, "target inline values", + dtohl(data_header->target_inline_entry_value_count)); + if (target_inline_entry_values == nullptr) { + return {}; + } + + auto configurations = ReadType<ConfigDescription>( + &data_ptr, &data_size, "configurations", + dtohl(data_header->configuration_count)); + if (configurations == nullptr) { + return {}; + } + auto overlay_entries = ReadType<Idmap_overlay_entry>(&data_ptr, &data_size, "target inline", dtohl(data_header->overlay_entry_count)); if (overlay_entries == nullptr) { @@ -328,9 +362,9 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, // Can't use make_unique because LoadedIdmap constructor is private. return std::unique_ptr<LoadedIdmap>( - new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries, - target_inline_entries, overlay_entries, std::move(idmap_string_pool), - *target_path, *overlay_path)); + new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries, + target_inline_entries, target_inline_entry_values, configurations, + overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path)); } bool LoadedIdmap::IsUpToDate() const { diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index 35b6170fae5b..c0fdfe25da21 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -88,7 +88,9 @@ static bool VerifyResTableType(incfs::map_ptr<ResTable_type> header) { // Make sure that there is enough room for the entry offsets. const size_t offsets_offset = dtohs(header->header.headerSize); const size_t entries_offset = dtohl(header->entriesStart); - const size_t offsets_length = sizeof(uint32_t) * entry_count; + const size_t offsets_length = header->flags & ResTable_type::FLAG_OFFSET16 + ? sizeof(uint16_t) * entry_count + : sizeof(uint32_t) * entry_count; if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) { LOG(ERROR) << "RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data."; @@ -107,8 +109,8 @@ static bool VerifyResTableType(incfs::map_ptr<ResTable_type> header) { return true; } -static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry( - incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) { +static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> +VerifyResTableEntry(incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) { // Check that the offset is aligned. if (UNLIKELY(entry_offset & 0x03U)) { LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned."; @@ -136,7 +138,7 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry( return base::unexpected(IOError::PAGES_MISSING); } - const size_t entry_size = dtohs(entry->size); + const size_t entry_size = entry->size(); if (UNLIKELY(entry_size < sizeof(entry.value()))) { LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset << " is too small."; @@ -149,6 +151,11 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry( return base::unexpected(std::nullopt); } + // If entry is compact, value is already encoded, and a compact entry + // cannot be a map_entry, we are done verifying + if (entry->is_compact()) + return entry.verified(); + if (entry_size < sizeof(ResTable_map_entry)) { // There needs to be room for one Res_value struct. if (UNLIKELY(entry_offset + entry_size > chunk_size - sizeof(Res_value))) { @@ -192,7 +199,7 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry( return base::unexpected(std::nullopt); } } - return {}; + return entry.verified(); } LoadedPackage::iterator::iterator(const LoadedPackage* lp, size_t ti, size_t ei) @@ -228,7 +235,7 @@ uint32_t LoadedPackage::iterator::operator*() const { entryIndex_); } -base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry( +base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry( incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index) { base::expected<uint32_t, NullOrIOError> entry_offset = GetEntryOffset(type_chunk, entry_index); if (UNLIKELY(!entry_offset.has_value())) { @@ -242,14 +249,13 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::GetEntryOffset( // The configuration matches and is better than the previous selection. // Find the entry value if it exists for this configuration. const size_t entry_count = dtohl(type_chunk->entryCount); - const size_t offsets_offset = dtohs(type_chunk->header.headerSize); + const auto offsets = type_chunk.offset(dtohs(type_chunk->header.headerSize)); // Check if there is the desired entry in this type. if (type_chunk->flags & ResTable_type::FLAG_SPARSE) { // This is encoded as a sparse map, so perform a binary search. bool error = false; - auto sparse_indices = type_chunk.offset(offsets_offset) - .convert<ResTable_sparseTypeEntry>().iterator(); + auto sparse_indices = offsets.convert<ResTable_sparseTypeEntry>().iterator(); auto sparse_indices_end = sparse_indices + entry_count; auto result = std::lower_bound(sparse_indices, sparse_indices_end, entry_index, [&error](const incfs::map_ptr<ResTable_sparseTypeEntry>& entry, @@ -284,26 +290,36 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::GetEntryOffset( return base::unexpected(std::nullopt); } - const auto entry_offset_ptr = type_chunk.offset(offsets_offset).convert<uint32_t>() + entry_index; - if (UNLIKELY(!entry_offset_ptr)) { - return base::unexpected(IOError::PAGES_MISSING); + uint32_t result; + + if (type_chunk->flags & ResTable_type::FLAG_OFFSET16) { + const auto entry_offset_ptr = offsets.convert<uint16_t>() + entry_index; + if (UNLIKELY(!entry_offset_ptr)) { + return base::unexpected(IOError::PAGES_MISSING); + } + result = offset_from16(entry_offset_ptr.value()); + } else { + const auto entry_offset_ptr = offsets.convert<uint32_t>() + entry_index; + if (UNLIKELY(!entry_offset_ptr)) { + return base::unexpected(IOError::PAGES_MISSING); + } + result = dtohl(entry_offset_ptr.value()); } - const uint32_t value = dtohl(entry_offset_ptr.value()); - if (value == ResTable_type::NO_ENTRY) { + if (result == ResTable_type::NO_ENTRY) { return base::unexpected(std::nullopt); } - - return value; + return result; } -base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntryFromOffset( - incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset) { +base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> +LoadedPackage::GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk, + uint32_t offset) { auto valid = VerifyResTableEntry(type_chunk, offset); if (UNLIKELY(!valid.has_value())) { return base::unexpected(valid.error()); } - return type_chunk.offset(offset + dtohl(type_chunk->entriesStart)).convert<ResTable_entry>(); + return valid; } base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations( @@ -376,31 +392,42 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::FindEntryByName( for (const auto& type_entry : type_spec->type_entries) { const incfs::verified_map_ptr<ResTable_type>& type = type_entry.type; - size_t entry_count = dtohl(type->entryCount); - for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) { - auto entry_offset_ptr = type.offset(dtohs(type->header.headerSize)).convert<uint32_t>() + - entry_idx; - if (!entry_offset_ptr) { - return base::unexpected(IOError::PAGES_MISSING); - } + const size_t entry_count = dtohl(type->entryCount); + const auto entry_offsets = type.offset(dtohs(type->header.headerSize)); + for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) { uint32_t offset; uint16_t res_idx; if (type->flags & ResTable_type::FLAG_SPARSE) { - auto sparse_entry = entry_offset_ptr.convert<ResTable_sparseTypeEntry>(); + auto sparse_entry = entry_offsets.convert<ResTable_sparseTypeEntry>() + entry_idx; + if (!sparse_entry) { + return base::unexpected(IOError::PAGES_MISSING); + } offset = dtohs(sparse_entry->offset) * 4u; res_idx = dtohs(sparse_entry->idx); + } else if (type->flags & ResTable_type::FLAG_OFFSET16) { + auto entry = entry_offsets.convert<uint16_t>() + entry_idx; + if (!entry) { + return base::unexpected(IOError::PAGES_MISSING); + } + offset = offset_from16(entry.value()); + res_idx = entry_idx; } else { - offset = dtohl(entry_offset_ptr.value()); + auto entry = entry_offsets.convert<uint32_t>() + entry_idx; + if (!entry) { + return base::unexpected(IOError::PAGES_MISSING); + } + offset = dtohl(entry.value()); res_idx = entry_idx; } + if (offset != ResTable_type::NO_ENTRY) { auto entry = type.offset(dtohl(type->entriesStart) + offset).convert<ResTable_entry>(); if (!entry) { return base::unexpected(IOError::PAGES_MISSING); } - if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) { + if (entry->key() == static_cast<uint32_t>(*key_idx)) { // The package ID will be overridden by the caller (due to runtime assignment of package // IDs for shared libraries). return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx); @@ -618,16 +645,16 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } std::string name; - util::ReadUtf16StringFromDevice(overlayable->name, arraysize(overlayable->name), &name); + util::ReadUtf16StringFromDevice(overlayable->name, std::size(overlayable->name), &name); std::string actor; - util::ReadUtf16StringFromDevice(overlayable->actor, arraysize(overlayable->actor), &actor); - - if (loaded_package->overlayable_map_.find(name) != - loaded_package->overlayable_map_.end()) { - LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" << name << "'."; + util::ReadUtf16StringFromDevice(overlayable->actor, std::size(overlayable->actor), &actor); + auto [name_to_actor_it, inserted] = + loaded_package->overlayable_map_.emplace(std::move(name), std::move(actor)); + if (!inserted) { + LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" + << name_to_actor_it->first << "'."; return {}; } - loaded_package->overlayable_map_.emplace(name, actor); // Iterate over the overlayable policy chunks contained within the overlayable chunk data ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size()); @@ -642,7 +669,6 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small."; return {}; } - if ((overlayable_child_chunk.data_size() / sizeof(ResTable_ref)) < dtohl(policy_header->entry_count)) { LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small to hold entries."; @@ -664,8 +690,8 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, // Add the pairing of overlayable properties and resource ids to the package OverlayableInfo overlayable_info { - .name = name, - .actor = actor, + .name = name_to_actor_it->first, + .actor = name_to_actor_it->second, .policy_flags = policy_header->policy_flags }; loaded_package->overlayable_infos_.emplace_back(std::move(overlayable_info), std::move(ids)); @@ -709,6 +735,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, const auto entry_end = entry_begin + dtohl(lib_alias->count); std::unordered_set<uint32_t> finalized_ids; finalized_ids.reserve(entry_end - entry_begin); + loaded_package->alias_id_map_.reserve(entry_end - entry_begin); for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) { if (!entry_iter) { LOG(ERROR) << "NULL ResTable_staged_alias_entry record??"; @@ -722,13 +749,20 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } auto staged_id = dtohl(entry_iter->stagedResId); - auto [_, success] = loaded_package->alias_id_map_.emplace(staged_id, finalized_id); - if (!success) { + loaded_package->alias_id_map_.emplace_back(staged_id, finalized_id); + } + + std::sort(loaded_package->alias_id_map_.begin(), loaded_package->alias_id_map_.end(), + [](auto&& l, auto&& r) { return l.first < r.first; }); + const auto duplicate_it = + std::adjacent_find(loaded_package->alias_id_map_.begin(), + loaded_package->alias_id_map_.end(), + [](auto&& l, auto&& r) { return l.first == r.first; }); + if (duplicate_it != loaded_package->alias_id_map_.end()) { LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.", - staged_id); + duplicate_it->first); return {}; } - } } break; default: @@ -820,6 +854,13 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, return true; } +bool LoadedArsc::LoadStringPool(const LoadedIdmap* loaded_idmap) { + if (loaded_idmap != nullptr) { + global_string_pool_ = util::make_unique<OverlayStringPool>(loaded_idmap); + } + return true; +} + std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data, const size_t length, const LoadedIdmap* loaded_idmap, @@ -855,6 +896,16 @@ std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data, return loaded_arsc; } +std::unique_ptr<LoadedArsc> LoadedArsc::Load(const LoadedIdmap* loaded_idmap) { + ATRACE_NAME("LoadedArsc::Load"); + + // Not using make_unique because the constructor is private. + std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc()); + loaded_arsc->LoadStringPool(loaded_idmap); + return loaded_arsc; +} + + std::unique_ptr<LoadedArsc> LoadedArsc::CreateEmpty() { return std::unique_ptr<LoadedArsc>(new LoadedArsc()); } diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp index d87a3ce72177..272a988ec55a 100644 --- a/libs/androidfw/Locale.cpp +++ b/libs/androidfw/Locale.cpp @@ -66,7 +66,7 @@ static inline bool is_number(const std::string& str) { return std::all_of(std::begin(str), std::end(str), ::isdigit); } -bool LocaleValue::InitFromFilterString(const StringPiece& str) { +bool LocaleValue::InitFromFilterString(StringPiece str) { // A locale (as specified in the filter) is an underscore separated name such // as "en_US", "en_Latn_US", or "en_US_POSIX". std::vector<std::string> parts = util::SplitAndLowercase(str, '_'); @@ -132,11 +132,11 @@ bool LocaleValue::InitFromFilterString(const StringPiece& str) { return true; } -bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) { +bool LocaleValue::InitFromBcp47Tag(StringPiece bcp47tag) { return InitFromBcp47TagImpl(bcp47tag, '-'); } -bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) { +bool LocaleValue::InitFromBcp47TagImpl(StringPiece bcp47tag, const char separator) { std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator); if (subtags.size() == 1) { set_language(subtags[0].c_str()); diff --git a/libs/androidfw/PosixUtils.cpp b/libs/androidfw/PosixUtils.cpp index 026912883a73..8ddc57240129 100644 --- a/libs/androidfw/PosixUtils.cpp +++ b/libs/androidfw/PosixUtils.cpp @@ -17,7 +17,7 @@ #ifdef _WIN32 // nothing to see here #else -#include <memory> +#include <optional> #include <string> #include <vector> @@ -29,45 +29,42 @@ #include "androidfw/PosixUtils.h" -namespace { - -std::unique_ptr<std::string> ReadFile(int fd) { - std::unique_ptr<std::string> str(new std::string()); +static std::optional<std::string> ReadFile(int fd) { + std::string str; char buf[1024]; ssize_t r; while ((r = read(fd, buf, sizeof(buf))) > 0) { - str->append(buf, r); + str.append(buf, r); } if (r != 0) { - return nullptr; + return std::nullopt; } - return str; -} - + return std::move(str); } namespace android { namespace util { -std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv) { - int stdout[2]; // stdout[0] read, stdout[1] write +ProcResult ExecuteBinary(const std::vector<std::string>& argv) { + int stdout[2]; // [0] read, [1] write if (pipe(stdout) != 0) { - PLOG(ERROR) << "pipe"; - return nullptr; + PLOG(ERROR) << "out pipe"; + return ProcResult{-1}; } - int stderr[2]; // stdout[0] read, stdout[1] write + int stderr[2]; // [0] read, [1] write if (pipe(stderr) != 0) { - PLOG(ERROR) << "pipe"; + PLOG(ERROR) << "err pipe"; close(stdout[0]); close(stdout[1]); - return nullptr; + return ProcResult{-1}; } auto gid = getgid(); auto uid = getuid(); - char const** argv0 = (char const**)malloc(sizeof(char*) * (argv.size() + 1)); + // better keep no C++ objects going into the child here + auto argv0 = (char const**)malloc(sizeof(char*) * (argv.size() + 1)); for (size_t i = 0; i < argv.size(); i++) { argv0[i] = argv[i].c_str(); } @@ -76,8 +73,12 @@ std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv) switch (pid) { case -1: // error free(argv0); + close(stdout[0]); + close(stdout[1]); + close(stderr[0]); + close(stderr[1]); PLOG(ERROR) << "fork"; - return nullptr; + return ProcResult{-1}; case 0: // child if (setgid(gid) != 0) { PLOG(ERROR) << "setgid"; @@ -109,17 +110,16 @@ std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv) if (!WIFEXITED(status)) { close(stdout[0]); close(stderr[0]); - return nullptr; + return ProcResult{-1}; } - std::unique_ptr<ProcResult> result(new ProcResult()); - result->status = status; - const auto out = ReadFile(stdout[0]); - result->stdout_str = out ? *out : ""; + ProcResult result(status); + auto out = ReadFile(stdout[0]); + result.stdout_str = out ? std::move(*out) : ""; close(stdout[0]); - const auto err = ReadFile(stderr[0]); - result->stderr_str = err ? *err : ""; + auto err = ReadFile(stderr[0]); + result.stderr_str = err ? std::move(*err) : ""; close(stderr[0]); - return result; + return std::move(result); } } diff --git a/libs/androidfw/ResourceTimer.cpp b/libs/androidfw/ResourceTimer.cpp new file mode 100644 index 000000000000..44128d9e4e3d --- /dev/null +++ b/libs/androidfw/ResourceTimer.cpp @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2022 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. + */ + +#include <unistd.h> +#include <string.h> + +#include <map> +#include <atomic> + +#include <utils/Log.h> +#include <androidfw/ResourceTimer.h> + +// The following block allows compilation on windows, which does not have getuid(). +#ifdef _WIN32 +#ifdef ERROR +#undef ERROR +#endif +#define getuid() (getUidWindows_) +#endif + +namespace android { + +namespace { + +#ifdef _WIN32 +// A temporary to confuse lint into thinking that getuid() on windows might return something other +// than zero. +int getUidWindows_ = 0; +#endif + +// The number of nanoseconds in a microsecond. +static const unsigned int US = 1000; +// The number of nanoseconds in a second. +static const unsigned int S = 1000 * 1000 * 1000; + +// Return the difference between two timespec values. The difference is in nanoseconds. If the +// return value would exceed 2s (2^31 nanoseconds) then UINT_MAX is returned. +unsigned int diffInNs(timespec const &a, timespec const &b) { + timespec r = { 0, 0 }; + r.tv_nsec = a.tv_nsec - b.tv_nsec; + if (r.tv_nsec < 0) { + r.tv_sec = -1; + r.tv_nsec += S; + } + r.tv_sec = r.tv_sec + (a.tv_sec - b.tv_sec); + if (r.tv_sec > 2) return UINT_MAX; + unsigned int result = (r.tv_sec * S) + r.tv_nsec; + if (result > 2 * S) return UINT_MAX; + return result; +} + +} + +ResourceTimer::ResourceTimer(Counter api) + : active_(enabled_.load()), + api_(api) { + if (active_) { + clock_gettime(CLOCK_MONOTONIC, &start_); + } +} + +ResourceTimer::~ResourceTimer() { + record(); +} + +void ResourceTimer::enable() { + if (!enabled_.load()) counter_ = new GuardedTimer[ResourceTimer::counterSize]; + enabled_.store(true); +} + +void ResourceTimer::cancel() { + active_ = false; +} + +void ResourceTimer::record() { + if (!active_) return; + + struct timespec end; + clock_gettime(CLOCK_MONOTONIC, &end); + // Get the difference in microseconds. + const unsigned int ticks = diffInNs(end, start_); + ScopedTimer t(counter_[toIndex(api_)]); + t->record(ticks); + active_ = false; +} + +bool ResourceTimer::copy(int counter, Timer &dst, bool reset) { + ScopedTimer t(counter_[counter]); + if (t->count == 0) { + dst.reset(); + if (reset) t->reset(); + return false; + } + Timer::copy(dst, *t, reset); + return true; +} + +void ResourceTimer::reset() { + for (int i = 0; i < counterSize; i++) { + ScopedTimer t(counter_[i]); + t->reset(); + } +} + +ResourceTimer::Timer::Timer() { + // Ensure newly-created objects are zeroed. + memset(buckets, 0, sizeof(buckets)); + reset(); +} + +ResourceTimer::Timer::~Timer() { + for (int d = 0; d < MaxDimension; d++) { + delete[] buckets[d]; + } +} + +void ResourceTimer::Timer::freeBuckets() { + for (int d = 0; d < MaxDimension; d++) { + delete[] buckets[d]; + buckets[d] = 0; + } +} + +void ResourceTimer::Timer::reset() { + count = total = mintime = maxtime = 0; + memset(largest, 0, sizeof(largest)); + memset(&pvalues, 0, sizeof(pvalues)); + // Zero the histogram, keeping any allocated dimensions. + for (int d = 0; d < MaxDimension; d++) { + if (buckets[d] != 0) memset(buckets[d], 0, sizeof(int) * MaxBuckets); + } +} + +void ResourceTimer::Timer::copy(Timer &dst, Timer &src, bool reset) { + dst.freeBuckets(); + dst = src; + // Clean up the histograms. + if (reset) { + // Do NOT free the src buckets because they being used by dst. + memset(src.buckets, 0, sizeof(src.buckets)); + src.reset(); + } else { + for (int d = 0; d < MaxDimension; d++) { + if (src.buckets[d] != nullptr) { + dst.buckets[d] = new int[MaxBuckets]; + memcpy(dst.buckets[d], src.buckets[d], sizeof(int) * MaxBuckets); + } + } + } +} + +void ResourceTimer::Timer::record(int ticks) { + // Record that the event happened. + count++; + + total += ticks; + if (mintime == 0 || ticks < mintime) mintime = ticks; + if (ticks > maxtime) maxtime = ticks; + + // Do not add oversized events to the histogram. + if (ticks != UINT_MAX) { + for (int d = 0; d < MaxDimension; d++) { + if (ticks < range[d]) { + if (buckets[d] == 0) { + buckets[d] = new int[MaxBuckets]; + memset(buckets[d], 0, sizeof(int) * MaxBuckets); + } + if (ticks < width[d]) { + // Special case: never write to bucket 0 because it complicates the percentile logic. + // However, this is always the smallest possible value to it is very unlikely to ever + // affect any of the percentile results. + buckets[d][1]++; + } else { + buckets[d][ticks / width[d]]++; + } + break; + } + } + } + + // The list of largest times is sorted with the biggest value at index 0 and the smallest at + // index MaxLargest-1. The incoming tick count should be added to the array only if it is + // larger than the current value at MaxLargest-1. + if (ticks > largest[Timer::MaxLargest-1]) { + for (size_t i = 0; i < Timer::MaxLargest; i++) { + if (ticks > largest[i]) { + if (i < Timer::MaxLargest-1) { + for (size_t j = Timer::MaxLargest - 1; j > i; j--) { + largest[j] = largest[j-1]; + } + } + largest[i] = ticks; + break; + } + } + } +} + +void ResourceTimer::Timer::Percentile::compute( + int cumulative, int current, int count, int width, int time) { + nominal = time; + nominal_actual = (cumulative * 100) / count; + floor = nominal - width; + floor_actual = ((cumulative - current) * 100) / count; +} + +void ResourceTimer::Timer::compute() { + memset(&pvalues, 0, sizeof(pvalues)); + + float l50 = count / 2.0; + float l90 = (count * 9.0) / 10.0; + float l95 = (count * 95.0) / 100.0; + float l99 = (count * 99.0) / 100.0; + + int sum = 0; + for (int d = 0; d < MaxDimension; d++) { + if (buckets[d] == 0) continue; + for (int j = 0; j < MaxBuckets && sum < count; j++) { + // Empty buckets don't contribute to the answers. Skip them. + if (buckets[d][j] == 0) continue; + sum += buckets[d][j]; + // A word on indexing. j is never zero in the following lines. buckets[0][0] corresponds + // to a delay of 0us, which cannot happen. buckets[n][0], for n > 0 overlaps a value in + // buckets[n-1], and the code would have stopped there. + if (sum >= l50 && pvalues.p50.nominal == 0) { + pvalues.p50.compute(sum, buckets[d][j], count, width[d], j * width[d]); + } + if (sum >= l90 && pvalues.p90.nominal == 0) { + pvalues.p90.compute(sum, buckets[d][j], count, width[d], j * width[d]); + } + if (sum >= l95 && pvalues.p95.nominal == 0) { + pvalues.p95.compute(sum, buckets[d][j], count, width[d], j * width[d]); + } + if (sum >= l99 && pvalues.p99.nominal == 0) { + pvalues.p99.compute(sum, buckets[d][j], count, width[d], j * width[d]); + } + } + } +} + +char const *ResourceTimer::toString(ResourceTimer::Counter counter) { + switch (counter) { + case Counter::GetResourceValue: + return "GetResourceValue"; + case Counter::RetrieveAttributes: + return "RetrieveAttributes"; + }; + return "Unknown"; +} + +std::atomic<bool> ResourceTimer::enabled_(false); +std::atomic<ResourceTimer::GuardedTimer *> ResourceTimer::counter_(nullptr); + +const int ResourceTimer::Timer::range[] = { 100 * US, 1000 * US, 10*1000 * US, 100*1000 * US }; +const int ResourceTimer::Timer::width[] = { 1 * US, 10 * US, 100 * US, 1000 * US }; + + +} // namespace android diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 5e8a623d4205..1fed2067d16e 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -33,7 +33,9 @@ #include <type_traits> #include <vector> +#include <android-base/file.h> #include <android-base/macros.h> +#include <android-base/utf8.h> #include <androidfw/ByteBucketArray.h> #include <androidfw/ResourceTypes.h> #include <androidfw/TypeWrappers.h> @@ -236,12 +238,23 @@ void Res_png_9patch::serialize(const Res_png_9patch& patch, const int32_t* xDivs } bool IsFabricatedOverlay(const std::string& path) { - std::ifstream fin(path); + return IsFabricatedOverlay(path.c_str()); +} + +bool IsFabricatedOverlay(const char* path) { + auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC)); + if (fd < 0) { + return false; + } + return IsFabricatedOverlay(fd); +} + +bool IsFabricatedOverlay(base::borrowed_fd fd) { uint32_t magic; - if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) { - return magic == kFabricatedOverlayMagic; + if (!base::ReadFullyAtOffset(fd, &magic, sizeof(magic), 0)) { + return false; } - return false; + return magic == kFabricatedOverlayMagic; } static bool assertIdmapHeader(const void* idmap, size_t size) { @@ -4487,20 +4500,14 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag return err; } - if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) { + if (entry.entry->map_entry()) { if (!mayBeBag) { ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID); } return BAD_VALUE; } - const Res_value* value = reinterpret_cast<const Res_value*>( - reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size); - - outValue->size = dtohs(value->size); - outValue->res0 = value->res0; - outValue->dataType = value->dataType; - outValue->data = dtohl(value->data); + *outValue = entry.entry->value(); // The reference may be pointing to a resource in a shared library. These // references have build-time generated package IDs. These ids may not match @@ -4691,11 +4698,10 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, return err; } - const uint16_t entrySize = dtohs(entry.entry->size); - const uint32_t parent = entrySize >= sizeof(ResTable_map_entry) - ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0; - const uint32_t count = entrySize >= sizeof(ResTable_map_entry) - ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0; + const uint16_t entrySize = entry.entry->size(); + const ResTable_map_entry* map_entry = entry.entry->map_entry(); + const uint32_t parent = map_entry ? dtohl(map_entry->parent.ident) : 0; + const uint32_t count = map_entry ? dtohl(map_entry->count) : 0; size_t N = count; @@ -4759,7 +4765,7 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, // Now merge in the new attributes... size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type)) - + dtohs(entry.entry->size); + + entrySize; const ResTable_map* map; bag_entry* entries = (bag_entry*)(set+1); size_t curEntry = 0; @@ -5137,7 +5143,7 @@ uint32_t ResTable::findEntry(const PackageGroup* group, ssize_t typeIndex, const continue; } - if (dtohl(entry->key.index) == (size_t) *ei) { + if (entry->key() == (size_t) *ei) { uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index()); if (outTypeSpecFlags) { Entry result; @@ -6600,8 +6606,12 @@ status_t ResTable::getEntry( // Entry does not exist. continue; } - - thisOffset = dtohl(eindex[realEntryIndex]); + if (thisType->flags & ResTable_type::FLAG_OFFSET16) { + auto eindex16 = reinterpret_cast<const uint16_t*>(eindex); + thisOffset = offset_from16(eindex16[realEntryIndex]); + } else { + thisOffset = dtohl(eindex[realEntryIndex]); + } } if (thisOffset == ResTable_type::NO_ENTRY) { @@ -6651,8 +6661,8 @@ status_t ResTable::getEntry( const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>( reinterpret_cast<const uint8_t*>(bestType) + bestOffset); - if (dtohs(entry->size) < sizeof(*entry)) { - ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size)); + if (entry->size() < sizeof(*entry)) { + ALOGW("ResTable_entry size 0x%zx is too small", entry->size()); return BAD_TYPE; } @@ -6663,7 +6673,7 @@ status_t ResTable::getEntry( outEntry->specFlags = specFlags; outEntry->package = bestPackage; outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset); - outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index)); + outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, entry->key()); } return NO_ERROR; } @@ -6880,7 +6890,8 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, const uint32_t typeSize = dtohl(type->header.size); const size_t newEntryCount = dtohl(type->entryCount); - + const size_t entrySize = type->flags & ResTable_type::FLAG_OFFSET16 ? + sizeof(uint16_t) : sizeof(uint32_t); if (kDebugLoadTableNoisy) { printf("Type off %p: type=0x%x, headerSize=0x%x, size=%u\n", (void*)(base-(const uint8_t*)chunk), @@ -6888,9 +6899,9 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, dtohs(type->header.headerSize), typeSize); } - if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*newEntryCount) > typeSize) { + if (dtohs(type->header.headerSize)+(entrySize*newEntryCount) > typeSize) { ALOGW("ResTable_type entry index to %p extends beyond chunk end 0x%x.", - (void*)(dtohs(type->header.headerSize) + (sizeof(uint32_t)*newEntryCount)), + (void*)(dtohs(type->header.headerSize) + (entrySize*newEntryCount)), typeSize); return (mError=BAD_TYPE); } @@ -6991,11 +7002,10 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {} DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib) - : mAssignedPackageId(packageId) + : mLookupTable() + , mAssignedPackageId(packageId) , mAppAsLib(appAsLib) { - memset(mLookupTable, 0, sizeof(mLookupTable)); - // Reserved package ids mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID; mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID; @@ -7076,10 +7086,6 @@ void DynamicRefTable::addMapping(uint8_t buildPackageId, uint8_t runtimePackageI mLookupTable[buildPackageId] = runtimePackageId; } -void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) { - mAliasId[stagedId] = finalizedId; -} - status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { uint32_t res = *resId; size_t packageId = Res_GETPACKAGE(res) + 1; @@ -7089,11 +7095,12 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { return NO_ERROR; } - auto alias_id = mAliasId.find(res); - if (alias_id != mAliasId.end()) { + const auto alias_it = std::lower_bound(mAliasId.begin(), mAliasId.end(), res, + [](const AliasMap::value_type& pair, uint32_t val) { return pair.first < val; }); + if (alias_it != mAliasId.end() && alias_it->first == res) { // Rewrite the resource id to its alias resource id. Since the alias resource id is a // compile-time id, it still needs to be resolved further. - res = alias_id->second; + res = alias_it->second; } if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) { @@ -7653,6 +7660,9 @@ void ResTable::print(bool inclValues) const if (type->flags & ResTable_type::FLAG_SPARSE) { printf(" [sparse]"); } + if (type->flags & ResTable_type::FLAG_OFFSET16) { + printf(" [offset16]"); + } } printf(":\n"); @@ -7684,7 +7694,13 @@ void ResTable::print(bool inclValues) const thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u; } else { entryId = entryIndex; - thisOffset = dtohl(eindex[entryIndex]); + if (type->flags & ResTable_type::FLAG_OFFSET16) { + const auto eindex16 = + reinterpret_cast<const uint16_t*>(eindex); + thisOffset = offset_from16(eindex16[entryIndex]); + } else { + thisOffset = dtohl(eindex[entryIndex]); + } if (thisOffset == ResTable_type::NO_ENTRY) { continue; } @@ -7734,7 +7750,7 @@ void ResTable::print(bool inclValues) const continue; } - uintptr_t esize = dtohs(ent->size); + uintptr_t esize = ent->size(); if ((esize&0x3) != 0) { printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize); continue; @@ -7746,30 +7762,27 @@ void ResTable::print(bool inclValues) const } const Res_value* valuePtr = NULL; - const ResTable_map_entry* bagPtr = NULL; + const ResTable_map_entry* bagPtr = ent->map_entry(); Res_value value; - if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) { + if (bagPtr) { printf("<bag>"); - bagPtr = (const ResTable_map_entry*)ent; } else { - valuePtr = (const Res_value*) - (((const uint8_t*)ent) + esize); - value.copyFrom_dtoh(*valuePtr); + value = ent->value(); printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)", (int)value.dataType, (int)value.data, (int)value.size, (int)value.res0); } - if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) { + if (ent->flags() & ResTable_entry::FLAG_PUBLIC) { printf(" (PUBLIC)"); } printf("\n"); if (inclValues) { - if (valuePtr != NULL) { + if (bagPtr == NULL) { printf(" "); print_value(typeConfigs->package, value); - } else if (bagPtr != NULL) { + } else { const int N = dtohl(bagPtr->count); const uint8_t* baseMapPtr = (const uint8_t*)ent; size_t mapOffset = esize; diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp index 87fb2c038c9f..ccb61561578f 100644 --- a/libs/androidfw/ResourceUtils.cpp +++ b/libs/androidfw/ResourceUtils.cpp @@ -18,7 +18,7 @@ namespace android { -bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type, +bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type, StringPiece* out_entry) { *out_package = ""; *out_type = ""; @@ -33,16 +33,16 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin while (current != end) { if (out_type->size() == 0 && *current == '/') { has_type_separator = true; - out_type->assign(start, current - start); + *out_type = StringPiece(start, current - start); start = current + 1; } else if (out_package->size() == 0 && *current == ':') { has_package_separator = true; - out_package->assign(start, current - start); + *out_package = StringPiece(start, current - start); start = current + 1; } current++; } - out_entry->assign(start, end - start); + *out_entry = StringPiece(start, end - start); return !(has_package_separator && out_package->empty()) && !(has_type_separator && out_type->empty()); @@ -50,7 +50,7 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName( const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref, - const StringPiece& package_name) { + StringPiece package_name) { AssetManager2::ResourceName name{ .package = package_name.data(), .package_len = package_name.size(), diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp new file mode 100644 index 000000000000..1cb8df311c89 --- /dev/null +++ b/libs/androidfw/StringPool.cpp @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2015 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. + */ + +#include <androidfw/BigBuffer.h> +#include <androidfw/StringPool.h> + +#include <algorithm> +#include <memory> +#include <string> + +#include "android-base/logging.h" +#include "androidfw/ResourceTypes.h" +#include "androidfw/StringPiece.h" +#include "androidfw/Util.h" + +using ::android::StringPiece; + +namespace android { + +StringPool::Ref::Ref() : entry_(nullptr) { +} + +StringPool::Ref::Ref(const StringPool::Ref& rhs) : entry_(rhs.entry_) { + if (entry_ != nullptr) { + entry_->ref_++; + } +} + +StringPool::Ref::Ref(StringPool::Entry* entry) : entry_(entry) { + if (entry_ != nullptr) { + entry_->ref_++; + } +} + +StringPool::Ref::~Ref() { + if (entry_ != nullptr) { + entry_->ref_--; + } +} + +StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) { + if (rhs.entry_ != nullptr) { + rhs.entry_->ref_++; + } + + if (entry_ != nullptr) { + entry_->ref_--; + } + entry_ = rhs.entry_; + return *this; +} + +bool StringPool::Ref::operator==(const Ref& rhs) const { + return entry_->value == rhs.entry_->value; +} + +bool StringPool::Ref::operator!=(const Ref& rhs) const { + return entry_->value != rhs.entry_->value; +} + +const std::string* StringPool::Ref::operator->() const { + return &entry_->value; +} + +const std::string& StringPool::Ref::operator*() const { + return entry_->value; +} + +size_t StringPool::Ref::index() const { + // Account for the styles, which *always* come first. + return entry_->pool_->styles_.size() + entry_->index_; +} + +const StringPool::Context& StringPool::Ref::GetContext() const { + return entry_->context; +} + +StringPool::StyleRef::StyleRef() : entry_(nullptr) { +} + +StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) : entry_(rhs.entry_) { + if (entry_ != nullptr) { + entry_->ref_++; + } +} + +StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : entry_(entry) { + if (entry_ != nullptr) { + entry_->ref_++; + } +} + +StringPool::StyleRef::~StyleRef() { + if (entry_ != nullptr) { + entry_->ref_--; + } +} + +StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) { + if (rhs.entry_ != nullptr) { + rhs.entry_->ref_++; + } + + if (entry_ != nullptr) { + entry_->ref_--; + } + entry_ = rhs.entry_; + return *this; +} + +bool StringPool::StyleRef::operator==(const StyleRef& rhs) const { + if (entry_->value != rhs.entry_->value) { + return false; + } + + if (entry_->spans.size() != rhs.entry_->spans.size()) { + return false; + } + + auto rhs_iter = rhs.entry_->spans.begin(); + for (const Span& span : entry_->spans) { + const Span& rhs_span = *rhs_iter; + if (span.first_char != rhs_span.first_char || span.last_char != rhs_span.last_char || + span.name != rhs_span.name) { + return false; + } + } + return true; +} + +bool StringPool::StyleRef::operator!=(const StyleRef& rhs) const { + return !operator==(rhs); +} + +const StringPool::StyleEntry* StringPool::StyleRef::operator->() const { + return entry_; +} + +const StringPool::StyleEntry& StringPool::StyleRef::operator*() const { + return *entry_; +} + +size_t StringPool::StyleRef::index() const { + return entry_->index_; +} + +const StringPool::Context& StringPool::StyleRef::GetContext() const { + return entry_->context; +} + +StringPool::Ref StringPool::MakeRef(StringPiece str) { + return MakeRefImpl(str, Context{}, true); +} + +StringPool::Ref StringPool::MakeRef(StringPiece str, const Context& context) { + return MakeRefImpl(str, context, true); +} + +StringPool::Ref StringPool::MakeRefImpl(StringPiece str, const Context& context, bool unique) { + if (unique) { + auto range = indexed_strings_.equal_range(str); + for (auto iter = range.first; iter != range.second; ++iter) { + if (context.priority == iter->second->context.priority) { + return Ref(iter->second); + } + } + } + + std::unique_ptr<Entry> entry(new Entry()); + entry->value = std::string(str); + entry->context = context; + entry->index_ = strings_.size(); + entry->ref_ = 0; + entry->pool_ = this; + + Entry* borrow = entry.get(); + strings_.emplace_back(std::move(entry)); + indexed_strings_.insert(std::make_pair(StringPiece(borrow->value), borrow)); + return Ref(borrow); +} + +StringPool::Ref StringPool::MakeRef(const Ref& ref) { + if (ref.entry_->pool_ == this) { + return ref; + } + return MakeRef(ref.entry_->value, ref.entry_->context); +} + +StringPool::StyleRef StringPool::MakeRef(const StyleString& str) { + return MakeRef(str, Context{}); +} + +StringPool::StyleRef StringPool::MakeRef(const StyleString& str, const Context& context) { + std::unique_ptr<StyleEntry> entry(new StyleEntry()); + entry->value = str.str; + entry->context = context; + entry->index_ = styles_.size(); + entry->ref_ = 0; + for (const android::Span& span : str.spans) { + entry->spans.emplace_back(Span{MakeRef(span.name), span.first_char, span.last_char}); + } + + StyleEntry* borrow = entry.get(); + styles_.emplace_back(std::move(entry)); + return StyleRef(borrow); +} + +StringPool::StyleRef StringPool::MakeRef(const StyleRef& ref) { + std::unique_ptr<StyleEntry> entry(new StyleEntry()); + entry->value = ref.entry_->value; + entry->context = ref.entry_->context; + entry->index_ = styles_.size(); + entry->ref_ = 0; + for (const Span& span : ref.entry_->spans) { + entry->spans.emplace_back(Span{MakeRef(*span.name), span.first_char, span.last_char}); + } + + StyleEntry* borrow = entry.get(); + styles_.emplace_back(std::move(entry)); + return StyleRef(borrow); +} + +void StringPool::ReAssignIndices() { + // Assign the style indices. + const size_t style_len = styles_.size(); + for (size_t index = 0; index < style_len; index++) { + styles_[index]->index_ = index; + } + + // Assign the string indices. + const size_t string_len = strings_.size(); + for (size_t index = 0; index < string_len; index++) { + strings_[index]->index_ = index; + } +} + +void StringPool::Merge(StringPool&& pool) { + // First, change the owning pool for the incoming strings. + for (std::unique_ptr<Entry>& entry : pool.strings_) { + entry->pool_ = this; + } + + // Now move the styles, strings, and indices over. + std::move(pool.styles_.begin(), pool.styles_.end(), std::back_inserter(styles_)); + pool.styles_.clear(); + std::move(pool.strings_.begin(), pool.strings_.end(), std::back_inserter(strings_)); + pool.strings_.clear(); + indexed_strings_.insert(pool.indexed_strings_.begin(), pool.indexed_strings_.end()); + pool.indexed_strings_.clear(); + + ReAssignIndices(); +} + +void StringPool::HintWillAdd(size_t string_count, size_t style_count) { + strings_.reserve(strings_.size() + string_count); + styles_.reserve(styles_.size() + style_count); +} + +void StringPool::Prune() { + const auto iter_end = indexed_strings_.end(); + auto index_iter = indexed_strings_.begin(); + while (index_iter != iter_end) { + if (index_iter->second->ref_ <= 0) { + index_iter = indexed_strings_.erase(index_iter); + } else { + ++index_iter; + } + } + + auto end_iter2 = + std::remove_if(strings_.begin(), strings_.end(), + [](const std::unique_ptr<Entry>& entry) -> bool { return entry->ref_ <= 0; }); + auto end_iter3 = std::remove_if( + styles_.begin(), styles_.end(), + [](const std::unique_ptr<StyleEntry>& entry) -> bool { return entry->ref_ <= 0; }); + + // Remove the entries at the end or else we'll be accessing a deleted string from the StyleEntry. + strings_.erase(end_iter2, strings_.end()); + styles_.erase(end_iter3, styles_.end()); + + ReAssignIndices(); +} + +template <typename E> +static void SortEntries( + std::vector<std::unique_ptr<E>>& entries, + const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) { + using UEntry = std::unique_ptr<E>; + + if (cmp != nullptr) { + std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool { + int r = cmp(a->context, b->context); + if (r == 0) { + r = a->value.compare(b->value); + } + return r < 0; + }); + } else { + std::sort(entries.begin(), entries.end(), + [](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; }); + } +} + +void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) { + SortEntries(styles_, cmp); + SortEntries(strings_, cmp); + ReAssignIndices(); +} + +template <typename T> +static T* EncodeLength(T* data, size_t length) { + static_assert(std::is_integral<T>::value, "wat."); + + constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); + constexpr size_t kMaxSize = kMask - 1; + if (length > kMaxSize) { + *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8))); + } + *data++ = length; + return data; +} + +/** + * Returns the maximum possible string length that can be successfully encoded + * using 2 units of the specified T. + * EncodeLengthMax<char> -> maximum unit length of 0x7FFF + * EncodeLengthMax<char16_t> -> maximum unit length of 0x7FFFFFFF + **/ +template <typename T> +static size_t EncodeLengthMax() { + static_assert(std::is_integral<T>::value, "wat."); + + constexpr size_t kMask = 1 << ((sizeof(T) * 8 * 2) - 1); + constexpr size_t max = kMask - 1; + return max; +} + +/** + * Returns the number of units (1 or 2) needed to encode the string length + * before writing the string. + */ +template <typename T> +static size_t EncodedLengthUnits(size_t length) { + static_assert(std::is_integral<T>::value, "wat."); + + constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); + constexpr size_t kMaxSize = kMask - 1; + return length > kMaxSize ? 2 : 1; +} + +const std::string kStringTooLarge = "STRING_TOO_LARGE"; + +static bool EncodeString(const std::string& str, const bool utf8, BigBuffer* out, + IDiagnostics* diag) { + if (utf8) { + const std::string& encoded = util::Utf8ToModifiedUtf8(str); + const ssize_t utf16_length = + utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(encoded.data()), encoded.size()); + CHECK(utf16_length >= 0); + + // Make sure the lengths to be encoded do not exceed the maximum length that + // can be encoded using chars + if ((((size_t)encoded.size()) > EncodeLengthMax<char>()) || + (((size_t)utf16_length) > EncodeLengthMax<char>())) { + diag->Error(DiagMessage() << "string too large to encode using UTF-8 " + << "written instead as '" << kStringTooLarge << "'"); + + EncodeString(kStringTooLarge, utf8, out, diag); + return false; + } + + const size_t total_size = EncodedLengthUnits<char>(utf16_length) + + EncodedLengthUnits<char>(encoded.size()) + encoded.size() + 1; + + char* data = out->NextBlock<char>(total_size); + + // First encode the UTF16 string length. + data = EncodeLength(data, utf16_length); + + // Now encode the size of the real UTF8 string. + data = EncodeLength(data, encoded.size()); + strncpy(data, encoded.data(), encoded.size()); + + } else { + const std::u16string encoded = util::Utf8ToUtf16(str); + const ssize_t utf16_length = encoded.size(); + + // Make sure the length to be encoded does not exceed the maximum possible + // length that can be encoded + if (((size_t)utf16_length) > EncodeLengthMax<char16_t>()) { + diag->Error(DiagMessage() << "string too large to encode using UTF-16 " + << "written instead as '" << kStringTooLarge << "'"); + + EncodeString(kStringTooLarge, utf8, out, diag); + return false; + } + + // Total number of 16-bit words to write. + const size_t total_size = EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1; + + char16_t* data = out->NextBlock<char16_t>(total_size); + + // Encode the actual UTF16 string length. + data = EncodeLength(data, utf16_length); + const size_t byte_length = encoded.size() * sizeof(char16_t); + + // NOTE: For some reason, strncpy16(data, entry->value.data(), + // entry->value.size()) truncates the string. + memcpy(data, encoded.data(), byte_length); + + // The null-terminating character is already here due to the block of data + // being set to 0s on allocation. + } + + return true; +} + +bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag) { + bool no_error = true; + const size_t start_index = out->size(); + android::ResStringPool_header* header = out->NextBlock<android::ResStringPool_header>(); + header->header.type = util::HostToDevice16(android::RES_STRING_POOL_TYPE); + header->header.headerSize = util::HostToDevice16(sizeof(*header)); + header->stringCount = util::HostToDevice32(pool.size()); + header->styleCount = util::HostToDevice32(pool.styles_.size()); + if (utf8) { + header->flags |= android::ResStringPool_header::UTF8_FLAG; + } + + uint32_t* indices = pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr; + uint32_t* style_indices = + pool.styles_.size() != 0 ? out->NextBlock<uint32_t>(pool.styles_.size()) : nullptr; + + const size_t before_strings_index = out->size(); + header->stringsStart = before_strings_index - start_index; + + // Styles always come first. + for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) { + *indices++ = out->size() - before_strings_index; + no_error = EncodeString(entry->value, utf8, out, diag) && no_error; + } + + for (const std::unique_ptr<Entry>& entry : pool.strings_) { + *indices++ = out->size() - before_strings_index; + no_error = EncodeString(entry->value, utf8, out, diag) && no_error; + } + + out->Align4(); + + if (style_indices != nullptr) { + const size_t before_styles_index = out->size(); + header->stylesStart = util::HostToDevice32(before_styles_index - start_index); + + for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) { + *style_indices++ = out->size() - before_styles_index; + + if (!entry->spans.empty()) { + android::ResStringPool_span* span = + out->NextBlock<android::ResStringPool_span>(entry->spans.size()); + for (const Span& s : entry->spans) { + span->name.index = util::HostToDevice32(s.name.index()); + span->firstChar = util::HostToDevice32(s.first_char); + span->lastChar = util::HostToDevice32(s.last_char); + span++; + } + } + + uint32_t* spanEnd = out->NextBlock<uint32_t>(); + *spanEnd = android::ResStringPool_span::END; + } + + // The error checking code in the platform looks for an entire + // ResStringPool_span structure worth of 0xFFFFFFFF at the end + // of the style block, so fill in the remaining 2 32bit words + // with 0xFFFFFFFF. + const size_t padding_length = + sizeof(android::ResStringPool_span) - sizeof(android::ResStringPool_span::name); + uint8_t* padding = out->NextBlock<uint8_t>(padding_length); + memset(padding, 0xff, padding_length); + out->Align4(); + } + header->header.size = util::HostToDevice32(out->size() - start_index); + return no_error; +} + +bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) { + return Flatten(out, pool, true, diag); +} + +bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) { + return Flatten(out, pool, false, diag); +} + +} // namespace android diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp index 647aa197a94d..70d14a11830e 100644 --- a/libs/androidfw/TypeWrappers.cpp +++ b/libs/androidfw/TypeWrappers.cpp @@ -59,7 +59,9 @@ const ResTable_entry* TypeVariant::iterator::operator*() const { + dtohl(type->header.size); const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>( reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize)); - if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount) > containerEnd) { + const size_t indexSize = type->flags & ResTable_type::FLAG_OFFSET16 ? + sizeof(uint16_t) : sizeof(uint32_t); + if (reinterpret_cast<uintptr_t>(entryIndices) + (indexSize * entryCount) > containerEnd) { ALOGE("Type's entry indices extend beyond its boundaries"); return NULL; } @@ -73,6 +75,9 @@ const ResTable_entry* TypeVariant::iterator::operator*() const { } entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u; + } else if (type->flags & ResTable_type::FLAG_OFFSET16) { + auto entryIndices16 = reinterpret_cast<const uint16_t*>(entryIndices); + entryOffset = offset_from16(entryIndices16[mIndex]); } else { entryOffset = dtohl(entryIndices[mIndex]); } @@ -91,11 +96,11 @@ const ResTable_entry* TypeVariant::iterator::operator*() const { if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) { ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex); return NULL; - } else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) { + } else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) { ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex); return NULL; - } else if (dtohs(entry->size) < sizeof(*entry)) { - ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size)); + } else if (entry->size() < sizeof(*entry)) { + ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size()); return NULL; } return entry; diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp index 59c9d640bb91..be55fe8b4bb6 100644 --- a/libs/androidfw/Util.cpp +++ b/libs/androidfw/Util.cpp @@ -42,7 +42,7 @@ void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out } } -std::u16string Utf8ToUtf16(const StringPiece& utf8) { +std::u16string Utf8ToUtf16(StringPiece utf8) { ssize_t utf16_length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length()); if (utf16_length <= 0) { @@ -56,7 +56,7 @@ std::u16string Utf8ToUtf16(const StringPiece& utf8) { return utf16; } -std::string Utf16ToUtf8(const StringPiece16& utf16) { +std::string Utf16ToUtf8(StringPiece16 utf16) { ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length()); if (utf8_length <= 0) { return {}; @@ -68,28 +68,151 @@ std::string Utf16ToUtf8(const StringPiece16& utf16) { return utf8; } -static std::vector<std::string> SplitAndTransform( - const StringPiece& str, char sep, const std::function<char(char)>& f) { +std::string Utf8ToModifiedUtf8(std::string_view utf8) { + // Java uses Modified UTF-8 which only supports the 1, 2, and 3 byte formats of UTF-8. To encode + // 4 byte UTF-8 codepoints, Modified UTF-8 allows the use of surrogate pairs in the same format + // of CESU-8 surrogate pairs. Calculate the size of the utf8 string with all 4 byte UTF-8 + // codepoints replaced with 2 3 byte surrogate pairs + size_t modified_size = 0; + const size_t size = utf8.size(); + for (size_t i = 0; i < size; i++) { + if (((uint8_t)utf8[i] >> 4) == 0xF) { + modified_size += 6; + i += 3; + } else { + modified_size++; + } + } + + // Early out if no 4 byte codepoints are found + if (size == modified_size) { + return std::string(utf8); + } + + std::string output; + output.reserve(modified_size); + for (size_t i = 0; i < size; i++) { + if (((uint8_t)utf8[i] >> 4) == 0xF) { + int32_t codepoint = utf32_from_utf8_at(utf8.data(), size, i, nullptr); + + // Calculate the high and low surrogates as UTF-16 would + int32_t high = ((codepoint - 0x10000) / 0x400) + 0xD800; + int32_t low = ((codepoint - 0x10000) % 0x400) + 0xDC00; + + // Encode each surrogate in UTF-8 + output.push_back((char)(0xE4 | ((high >> 12) & 0xF))); + output.push_back((char)(0x80 | ((high >> 6) & 0x3F))); + output.push_back((char)(0x80 | (high & 0x3F))); + output.push_back((char)(0xE4 | ((low >> 12) & 0xF))); + output.push_back((char)(0x80 | ((low >> 6) & 0x3F))); + output.push_back((char)(0x80 | (low & 0x3F))); + i += 3; + } else { + output.push_back(utf8[i]); + } + } + + return output; +} + +std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8) { + // The UTF-8 representation will have a byte length less than or equal to the Modified UTF-8 + // representation. + std::string output; + output.reserve(modified_utf8.size()); + + size_t index = 0; + const size_t modified_size = modified_utf8.size(); + while (index < modified_size) { + size_t next_index; + int32_t high_surrogate = + utf32_from_utf8_at(modified_utf8.data(), modified_size, index, &next_index); + if (high_surrogate < 0) { + return {}; + } + + // Check that the first codepoint is within the high surrogate range + if (high_surrogate >= 0xD800 && high_surrogate <= 0xDB7F) { + int32_t low_surrogate = + utf32_from_utf8_at(modified_utf8.data(), modified_size, next_index, &next_index); + if (low_surrogate < 0) { + return {}; + } + + // Check that the second codepoint is within the low surrogate range + if (low_surrogate >= 0xDC00 && low_surrogate <= 0xDFFF) { + const char32_t codepoint = + (char32_t)(((high_surrogate - 0xD800) * 0x400) + (low_surrogate - 0xDC00) + 0x10000); + + // The decoded codepoint should represent a 4 byte, UTF-8 character + const size_t utf8_length = (size_t)utf32_to_utf8_length(&codepoint, 1); + if (utf8_length != 4) { + return {}; + } + + // Encode the UTF-8 representation of the codepoint into the string + const size_t start_index = output.size(); + output.resize(start_index + utf8_length); + char* start = &output[start_index]; + utf32_to_utf8((char32_t*)&codepoint, 1, start, utf8_length + 1); + + index = next_index; + continue; + } + } + + // Append non-surrogate pairs to the output string + for (size_t i = index; i < next_index; i++) { + output.push_back(modified_utf8[i]); + } + index = next_index; + } + return output; +} + +template <class Func> +static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, Func&& f) { std::vector<std::string> parts; const StringPiece::const_iterator end = std::end(str); StringPiece::const_iterator start = std::begin(str); StringPiece::const_iterator current; do { current = std::find(start, end, sep); - parts.emplace_back(str.substr(start, current).to_string()); - if (f) { - std::string& part = parts.back(); - std::transform(part.begin(), part.end(), part.begin(), f); - } + parts.emplace_back(StringPiece(start, current - start)); + std::string& part = parts.back(); + std::transform(part.begin(), part.end(), part.begin(), f); start = current + 1; } while (current != end); return parts; } -std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) { - return SplitAndTransform(str, sep, ::tolower); +std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) { + return SplitAndTransform(str, sep, [](char c) { return ::tolower(c); }); } +std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) { + auto data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]); + uint8_t* p = data.get(); + for (const auto& block : buffer) { + memcpy(p, block.buffer.get(), block.size); + p += block.size; + } + return data; +} + +StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx) { + if (auto str = pool.stringAt(idx); str.ok()) { + return *str; + } + return StringPiece16(); +} + +std::string GetString(const android::ResStringPool& pool, size_t idx) { + if (auto str = pool.string8At(idx); str.ok()) { + return ModifiedUtf8ToUtf8(*str); + } + return Utf16ToUtf8(GetString16(pool, idx)); +} } // namespace util } // namespace android diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 1bde792da2ba..f10cb9bf480a 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -17,6 +17,7 @@ #ifndef ANDROIDFW_ASSETMANAGER2_H_ #define ANDROIDFW_ASSETMANAGER2_H_ +#include "android-base/function_ref.h" #include "android-base/macros.h" #include <array> @@ -104,7 +105,7 @@ class AssetManager2 { // new resource IDs. bool SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches = true); - inline const std::vector<const ApkAssets*> GetApkAssets() const { + inline const std::vector<const ApkAssets*>& GetApkAssets() const { return apk_assets_; } @@ -124,8 +125,7 @@ class AssetManager2 { uint8_t GetAssignedPackageId(const LoadedPackage* package) const; // Returns a string representation of the overlayable API of a package. - bool GetOverlayablesToString(const android::StringPiece& package_name, - std::string* out) const; + bool GetOverlayablesToString(android::StringPiece package_name, std::string* out) const; const std::unordered_map<std::string, std::string>* GetOverlayableMapForPackage( uint32_t package_id) const; @@ -321,17 +321,8 @@ class AssetManager2 { // Creates a new Theme from this AssetManager. std::unique_ptr<Theme> NewTheme(); - void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func, - package_property_t excluded_property_flags = 0U) const { - for (const PackageGroup& package_group : package_groups_) { - const auto loaded_package = package_group.packages_.front().loaded_package_; - if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U - && !func(loaded_package->GetPackageName(), - package_group.dynamic_ref_table->mAssignedPackageId)) { - return; - } - } - } + void ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func, + package_property_t excluded_property_flags = 0U) const; void DumpToLog() const; @@ -572,6 +563,7 @@ class Theme { AssetManager2* asset_manager_ = nullptr; uint32_t type_spec_flags_ = 0u; + std::vector<uint32_t> keys_; std::vector<Entry> entries_; }; diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h index 966ec74c1786..d33c325ff369 100644 --- a/libs/androidfw/include/androidfw/AssetsProvider.h +++ b/libs/androidfw/include/androidfw/AssetsProvider.h @@ -20,6 +20,7 @@ #include <memory> #include <string> +#include "android-base/function_ref.h" #include "android-base/macros.h" #include "android-base/unique_fd.h" @@ -46,7 +47,7 @@ struct AssetsProvider { // Iterate over all files and directories provided by the interface. The order of iteration is // stable. virtual bool ForEachFile(const std::string& path, - const std::function<void(const StringPiece&, FileType)>& f) const = 0; + base::function_ref<void(StringPiece, FileType)> f) const = 0; // Retrieves the path to the contents of the AssetsProvider on disk. The path could represent an // APk, a directory, or some other file type. @@ -80,8 +81,8 @@ struct AssetsProvider { // Supplies assets from a zip archive. struct ZipAssetsProvider : public AssetsProvider { - static std::unique_ptr<ZipAssetsProvider> Create(std::string path, - package_property_t flags); + static std::unique_ptr<ZipAssetsProvider> Create(std::string path, package_property_t flags, + base::unique_fd fd = {}); static std::unique_ptr<ZipAssetsProvider> Create(base::unique_fd fd, std::string friendly_name, @@ -90,7 +91,7 @@ struct ZipAssetsProvider : public AssetsProvider { off64_t len = kUnknownLength); bool ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) const override; + base::function_ref<void(StringPiece, FileType)> f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; @@ -108,7 +109,12 @@ struct ZipAssetsProvider : public AssetsProvider { time_t last_mod_time); struct PathOrDebugName { - PathOrDebugName(std::string&& value, bool is_path); + static PathOrDebugName Path(std::string value) { + return {std::move(value), true}; + } + static PathOrDebugName DebugName(std::string value) { + return {std::move(value), false}; + } // Retrieves the path or null if this class represents a debug name. WARN_UNUSED const std::string* GetPath() const; @@ -117,11 +123,16 @@ struct ZipAssetsProvider : public AssetsProvider { WARN_UNUSED const std::string& GetDebugName() const; private: + PathOrDebugName(std::string value, bool is_path) : value_(std::move(value)), is_path_(is_path) { + } std::string value_; bool is_path_; }; - std::unique_ptr<ZipArchive, void (*)(ZipArchive*)> zip_handle_; + struct ZipCloser { + void operator()(ZipArchive* a) const; + }; + std::unique_ptr<ZipArchive, ZipCloser> zip_handle_; PathOrDebugName name_; package_property_t flags_; time_t last_mod_time_; @@ -132,7 +143,7 @@ struct DirectoryAssetsProvider : public AssetsProvider { static std::unique_ptr<DirectoryAssetsProvider> Create(std::string root_dir); bool ForEachFile(const std::string& path, - const std::function<void(const StringPiece&, FileType)>& f) const override; + base::function_ref<void(StringPiece, FileType)> f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; @@ -157,7 +168,7 @@ struct MultiAssetsProvider : public AssetsProvider { std::unique_ptr<AssetsProvider>&& secondary); bool ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) const override; + base::function_ref<void(StringPiece, FileType)> f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; @@ -181,10 +192,10 @@ struct MultiAssetsProvider : public AssetsProvider { // Does not provide any assets. struct EmptyAssetsProvider : public AssetsProvider { static std::unique_ptr<AssetsProvider> Create(); - static std::unique_ptr<AssetsProvider> Create(const std::string& path); + static std::unique_ptr<AssetsProvider> Create(std::string path); bool ForEachFile(const std::string& path, - const std::function<void(const StringPiece&, FileType)>& f) const override; + base::function_ref<void(StringPiece, FileType)> f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; diff --git a/libs/androidfw/include/androidfw/BigBuffer.h b/libs/androidfw/include/androidfw/BigBuffer.h new file mode 100644 index 000000000000..b99a4edf9d88 --- /dev/null +++ b/libs/androidfw/include/androidfw/BigBuffer.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef _ANDROID_BIG_BUFFER_H +#define _ANDROID_BIG_BUFFER_H + +#include <cstring> +#include <memory> +#include <string> +#include <type_traits> +#include <vector> + +#include "android-base/logging.h" +#include "android-base/macros.h" + +namespace android { + +/** + * Inspired by protobuf's ZeroCopyOutputStream, offers blocks of memory + * in which to write without knowing the full size of the entire payload. + * This is essentially a list of memory blocks. As one fills up, another + * block is allocated and appended to the end of the list. + */ +class BigBuffer { + public: + /** + * A contiguous block of allocated memory. + */ + struct Block { + /** + * Pointer to the memory. + */ + std::unique_ptr<uint8_t[]> buffer; + + /** + * Size of memory that is currently occupied. The actual + * allocation may be larger. + */ + size_t size; + + private: + friend class BigBuffer; + + /** + * The size of the memory block allocation. + */ + size_t block_size_; + }; + + typedef std::vector<Block>::const_iterator const_iterator; + + /** + * Create a BigBuffer with block allocation sizes + * of block_size. + */ + explicit BigBuffer(size_t block_size); + + BigBuffer(BigBuffer&& rhs) noexcept; + + /** + * Number of occupied bytes in all the allocated blocks. + */ + size_t size() const; + + /** + * Returns a pointer to an array of T, where T is + * a POD type. The elements are zero-initialized. + */ + template <typename T> + T* NextBlock(size_t count = 1); + + /** + * Returns the next block available and puts the size in out_count. + * This is useful for grabbing blocks where the size doesn't matter. + * Use BackUp() to give back any bytes that were not used. + */ + void* NextBlock(size_t* out_count); + + /** + * Backs up count bytes. This must only be called after NextBlock() + * and can not be larger than sizeof(T) * count of the last NextBlock() + * call. + */ + void BackUp(size_t count); + + /** + * Moves the specified BigBuffer into this one. When this method + * returns, buffer is empty. + */ + void AppendBuffer(BigBuffer&& buffer); + + /** + * Pads the block with 'bytes' bytes of zero values. + */ + void Pad(size_t bytes); + + /** + * Pads the block so that it aligns on a 4 byte boundary. + */ + void Align4(); + + size_t block_size() const; + + const_iterator begin() const; + const_iterator end() const; + + std::string to_string() const; + + private: + DISALLOW_COPY_AND_ASSIGN(BigBuffer); + + /** + * Returns a pointer to a buffer of the requested size. + * The buffer is zero-initialized. + */ + void* NextBlockImpl(size_t size); + + size_t block_size_; + size_t size_; + std::vector<Block> blocks_; +}; + +inline BigBuffer::BigBuffer(size_t block_size) : block_size_(block_size), size_(0) { +} + +inline BigBuffer::BigBuffer(BigBuffer&& rhs) noexcept + : block_size_(rhs.block_size_), size_(rhs.size_), blocks_(std::move(rhs.blocks_)) { +} + +inline size_t BigBuffer::size() const { + return size_; +} + +inline size_t BigBuffer::block_size() const { + return block_size_; +} + +template <typename T> +inline T* BigBuffer::NextBlock(size_t count) { + static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type"); + CHECK(count != 0); + return reinterpret_cast<T*>(NextBlockImpl(sizeof(T) * count)); +} + +inline void BigBuffer::BackUp(size_t count) { + Block& block = blocks_.back(); + block.size -= count; + size_ -= count; +} + +inline void BigBuffer::AppendBuffer(BigBuffer&& buffer) { + std::move(buffer.blocks_.begin(), buffer.blocks_.end(), std::back_inserter(blocks_)); + size_ += buffer.size_; + buffer.blocks_.clear(); + buffer.size_ = 0; +} + +inline void BigBuffer::Pad(size_t bytes) { + NextBlock<char>(bytes); +} + +inline void BigBuffer::Align4() { + const size_t unaligned = size_ % 4; + if (unaligned != 0) { + Pad(4 - unaligned); + } +} + +inline BigBuffer::const_iterator BigBuffer::begin() const { + return blocks_.begin(); +} + +inline BigBuffer::const_iterator BigBuffer::end() const { + return blocks_.end(); +} + +} // namespace android + +#endif // _ANDROID_BIG_BUFFER_H diff --git a/libs/androidfw/include/androidfw/ByteBucketArray.h b/libs/androidfw/include/androidfw/ByteBucketArray.h index 949c9445b3e8..ca0a9eda9caa 100644 --- a/libs/androidfw/include/androidfw/ByteBucketArray.h +++ b/libs/androidfw/include/androidfw/ByteBucketArray.h @@ -17,6 +17,7 @@ #ifndef __BYTE_BUCKET_ARRAY_H #define __BYTE_BUCKET_ARRAY_H +#include <algorithm> #include <cstdint> #include <cstring> @@ -31,14 +32,16 @@ namespace android { template <typename T> class ByteBucketArray { public: - ByteBucketArray() : default_() { memset(buckets_, 0, sizeof(buckets_)); } + ByteBucketArray() { + memset(buckets_, 0, sizeof(buckets_)); + } ~ByteBucketArray() { - for (size_t i = 0; i < kNumBuckets; i++) { - if (buckets_[i] != NULL) { - delete[] buckets_[i]; - } - } + deleteBuckets(); + } + + void clear() { + deleteBuckets(); memset(buckets_, 0, sizeof(buckets_)); } @@ -53,7 +56,7 @@ class ByteBucketArray { uint8_t bucket_index = static_cast<uint8_t>(index) >> 4; T* bucket = buckets_[bucket_index]; - if (bucket == NULL) { + if (bucket == nullptr) { return default_; } return bucket[0x0f & static_cast<uint8_t>(index)]; @@ -64,9 +67,9 @@ class ByteBucketArray { << ") with size=" << size(); uint8_t bucket_index = static_cast<uint8_t>(index) >> 4; - T* bucket = buckets_[bucket_index]; - if (bucket == NULL) { - bucket = buckets_[bucket_index] = new T[kBucketSize](); + T*& bucket = buckets_[bucket_index]; + if (bucket == nullptr) { + bucket = new T[kBucketSize](); } return bucket[0x0f & static_cast<uint8_t>(index)]; } @@ -80,11 +83,44 @@ class ByteBucketArray { return true; } + template <class Func> + void forEachItem(Func f) { + for (size_t i = 0; i < kNumBuckets; i++) { + const auto bucket = buckets_[i]; + if (bucket != nullptr) { + for (size_t j = 0; j < kBucketSize; j++) { + f((i << 4) | j, bucket[j]); + } + } + } + } + + template <class Func> + void trimBuckets(Func isEmptyFunc) { + for (size_t i = 0; i < kNumBuckets; i++) { + const auto bucket = buckets_[i]; + if (bucket != nullptr) { + if (std::all_of(bucket, bucket + kBucketSize, isEmptyFunc)) { + delete[] bucket; + buckets_[i] = nullptr; + } + } + } + } + private: enum { kNumBuckets = 16, kBucketSize = 16 }; + void deleteBuckets() { + for (size_t i = 0; i < kNumBuckets; i++) { + if (buckets_[i] != nullptr) { + delete[] buckets_[i]; + } + } + } + T* buckets_[kNumBuckets]; - T default_; + static inline const T default_ = {}; }; } // namespace android diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h index 61d10cd4e55b..71087cdfb6fa 100644 --- a/libs/androidfw/include/androidfw/ConfigDescription.h +++ b/libs/androidfw/include/androidfw/ConfigDescription.h @@ -72,7 +72,7 @@ struct ConfigDescription : public ResTable_config { * The resulting configuration has the appropriate sdkVersion defined * for backwards compatibility. */ - static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr); + static bool Parse(android::StringPiece str, ConfigDescription* out = nullptr); /** * If the configuration uses an axis that was added after diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h new file mode 100644 index 000000000000..4d5844eaa069 --- /dev/null +++ b/libs/androidfw/include/androidfw/IDiagnostics.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef _ANDROID_DIAGNOSTICS_H +#define _ANDROID_DIAGNOSTICS_H + +#include <sstream> +#include <string> + +#include "Source.h" +#include "android-base/macros.h" +#include "androidfw/StringPiece.h" + +namespace android { + +struct DiagMessageActual { + Source source; + std::string message; +}; + +struct DiagMessage { + public: + DiagMessage() = default; + + explicit DiagMessage(android::StringPiece src) : source_(src) { + } + + explicit DiagMessage(const Source& src) : source_(src) { + } + + explicit DiagMessage(size_t line) : source_(Source().WithLine(line)) { + } + + template <typename T> + DiagMessage& operator<<(const T& value) { + message_ << value; + return *this; + } + + DiagMessageActual Build() const { + return DiagMessageActual{source_, message_.str()}; + } + + private: + Source source_; + std::stringstream message_; +}; + +template <> +inline DiagMessage& DiagMessage::operator<<(const ::std::u16string& value) { + message_ << value; + return *this; +} + +struct IDiagnostics { + virtual ~IDiagnostics() = default; + + enum class Level { Note, Warn, Error }; + + virtual void Log(Level level, DiagMessageActual& actualMsg) = 0; + + virtual void Error(const DiagMessage& message) { + DiagMessageActual actual = message.Build(); + Log(Level::Error, actual); + } + + virtual void Warn(const DiagMessage& message) { + DiagMessageActual actual = message.Build(); + Log(Level::Warn, actual); + } + + virtual void Note(const DiagMessage& message) { + DiagMessageActual actual = message.Build(); + Log(Level::Note, actual); + } +}; + +class SourcePathDiagnostics : public IDiagnostics { + public: + SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : source_(src), diag_(diag) { + } + + void Log(Level level, DiagMessageActual& actual_msg) override { + actual_msg.source.path = source_.path; + diag_->Log(level, actual_msg); + if (level == Level::Error) { + error = true; + } + } + + bool HadError() { + return error; + } + + private: + Source source_; + IDiagnostics* diag_; + bool error = false; + + DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics); +}; + +class NoOpDiagnostics : public IDiagnostics { + public: + NoOpDiagnostics() = default; + + void Log(Level level, DiagMessageActual& actual_msg) override { + (void)level; + (void)actual_msg; + } + + DISALLOW_COPY_AND_ASSIGN(NoOpDiagnostics); +}; + +} // namespace android + +#endif /* _ANDROID_DIAGNOSTICS_H */ diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index 6804472b3d17..60689128dffb 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -23,6 +23,7 @@ #include <variant> #include "android-base/macros.h" +#include "androidfw/ConfigDescription.h" #include "androidfw/StringPiece.h" #include "androidfw/ResourceTypes.h" #include "utils/ByteOrder.h" @@ -35,6 +36,7 @@ struct Idmap_header; struct Idmap_data_header; struct Idmap_target_entry; struct Idmap_target_entry_inline; +struct Idmap_target_entry_inline_value; struct Idmap_overlay_entry; // A string pool for overlay apk assets. The string pool holds the strings of the overlay resources @@ -91,7 +93,8 @@ class IdmapResMap { public: Result() = default; explicit Result(uint32_t value) : data_(value) {}; - explicit Result(const Res_value& value) : data_(value) { }; + explicit Result(std::map<ConfigDescription, Res_value> value) : data_(std::move(value)) { + } // Returns `true` if the resource is overlaid. explicit operator bool() const { @@ -107,15 +110,16 @@ class IdmapResMap { } bool IsInlineValue() const { - return std::get_if<Res_value>(&data_) != nullptr; + return std::get_if<2>(&data_) != nullptr; } - const Res_value& GetInlineValue() const { - return std::get<Res_value>(data_); + const std::map<ConfigDescription, Res_value>& GetInlineValue() const { + return std::get<2>(data_); } private: - std::variant<std::monostate, uint32_t, Res_value> data_; + std::variant<std::monostate, uint32_t, + std::map<ConfigDescription, Res_value> > data_; }; // Looks up the value that overlays the target resource id. @@ -129,12 +133,16 @@ class IdmapResMap { explicit IdmapResMap(const Idmap_data_header* data_header, const Idmap_target_entry* entries, const Idmap_target_entry_inline* inline_entries, + const Idmap_target_entry_inline_value* inline_entry_values, + const ConfigDescription* configs, uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table); const Idmap_data_header* data_header_; const Idmap_target_entry* entries_; const Idmap_target_entry_inline* inline_entries_; + const Idmap_target_entry_inline_value* inline_entry_values_; + const ConfigDescription* configurations_; const uint8_t target_assigned_package_id_; const OverlayDynamicRefTable* overlay_ref_table_; @@ -149,8 +157,7 @@ class IdmapResMap { class LoadedIdmap { public: // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed. - static std::unique_ptr<LoadedIdmap> Load(const StringPiece& idmap_path, - const StringPiece& idmap_data); + static std::unique_ptr<LoadedIdmap> Load(StringPiece idmap_path, StringPiece idmap_data); // Returns the path to the IDMAP. std::string_view IdmapPath() const { @@ -170,8 +177,8 @@ class LoadedIdmap { // Returns a mapping from target resource ids to overlay values. const IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) const { - return IdmapResMap(data_header_, target_entries_, target_inline_entries_, - target_assigned_package_id, overlay_ref_table); + return IdmapResMap(data_header_, target_entries_, target_inline_entries_, inline_entry_values_, + configurations_, target_assigned_package_id, overlay_ref_table); } // Returns a dynamic reference table for a loaded overlay package. @@ -191,6 +198,8 @@ class LoadedIdmap { const Idmap_data_header* data_header_; const Idmap_target_entry* target_entries_; const Idmap_target_entry_inline* target_inline_entries_; + const Idmap_target_entry_inline_value* inline_entry_values_; + const ConfigDescription* configurations_; const Idmap_overlay_entry* overlay_entries_; const std::unique_ptr<ResStringPool> string_pool_; @@ -207,6 +216,8 @@ class LoadedIdmap { const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_target_entry_inline* target_inline_entries, + const Idmap_target_entry_inline_value* inline_entry_values_, + const ConfigDescription* configs, const Idmap_overlay_entry* overlay_entries, std::unique_ptr<ResStringPool>&& string_pool, std::string_view overlay_apk_path, diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index b3d6a4dcb955..4d12885ad291 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -99,8 +99,8 @@ enum : package_property_t { }; struct OverlayableInfo { - std::string name; - std::string actor; + std::string_view name; + std::string_view actor; uint32_t policy_flags; }; @@ -166,14 +166,14 @@ class LoadedPackage { base::expected<uint32_t, NullOrIOError> FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const; - static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntry( - incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index); + static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> + GetEntry(incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index); static base::expected<uint32_t, NullOrIOError> GetEntryOffset( incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index); - static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntryFromOffset( - incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset); + static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> + GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset); // Returns the string pool where type names are stored. const ResStringPool* GetTypeStringPool() const { @@ -275,7 +275,7 @@ class LoadedPackage { return overlayable_map_; } - const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const { + const std::vector<std::pair<uint32_t, uint32_t>>& GetAliasResourceIdMap() const { return alias_id_map_; } @@ -295,8 +295,8 @@ class LoadedPackage { std::unordered_map<uint8_t, TypeSpec> type_specs_; ByteBucketArray<uint32_t> resource_ids_; std::vector<DynamicPackageEntry> dynamic_package_map_; - std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_; - std::map<uint32_t, uint32_t> alias_id_map_; + std::vector<std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_; + std::vector<std::pair<uint32_t, uint32_t>> alias_id_map_; // A map of overlayable name to actor std::unordered_map<std::string, std::string> overlayable_map_; @@ -314,6 +314,8 @@ class LoadedArsc { const LoadedIdmap* loaded_idmap = nullptr, package_property_t property_flags = 0U); + static std::unique_ptr<LoadedArsc> Load(const LoadedIdmap* loaded_idmap = nullptr); + // Create an empty LoadedArsc. This is used when an APK has no resources.arsc. static std::unique_ptr<LoadedArsc> CreateEmpty(); @@ -338,6 +340,7 @@ class LoadedArsc { LoadedArsc() = default; bool LoadTable( const Chunk& chunk, const LoadedIdmap* loaded_idmap, package_property_t property_flags); + bool LoadStringPool(const LoadedIdmap* loaded_idmap); std::unique_ptr<ResStringPool> global_string_pool_ = util::make_unique<ResStringPool>(); std::vector<std::unique_ptr<const LoadedPackage>> packages_; diff --git a/libs/androidfw/include/androidfw/Locale.h b/libs/androidfw/include/androidfw/Locale.h index 484ed79a8efd..8934bed098fe 100644 --- a/libs/androidfw/include/androidfw/Locale.h +++ b/libs/androidfw/include/androidfw/Locale.h @@ -39,10 +39,10 @@ struct LocaleValue { /** * Initialize this LocaleValue from a config string. */ - bool InitFromFilterString(const android::StringPiece& config); + bool InitFromFilterString(android::StringPiece config); // Initializes this LocaleValue from a BCP-47 locale tag. - bool InitFromBcp47Tag(const android::StringPiece& bcp47tag); + bool InitFromBcp47Tag(android::StringPiece bcp47tag); /** * Initialize this LocaleValue from parts of a vector. @@ -70,7 +70,7 @@ struct LocaleValue { inline bool operator>(const LocaleValue& o) const; private: - bool InitFromBcp47TagImpl(const android::StringPiece& bcp47tag, const char separator); + bool InitFromBcp47TagImpl(android::StringPiece bcp47tag, const char separator); void set_language(const char* language); void set_region(const char* language); diff --git a/libs/androidfw/include/androidfw/PosixUtils.h b/libs/androidfw/include/androidfw/PosixUtils.h index bb2084740a44..c46e5e6b3fb5 100644 --- a/libs/androidfw/include/androidfw/PosixUtils.h +++ b/libs/androidfw/include/androidfw/PosixUtils.h @@ -25,12 +25,18 @@ struct ProcResult { int status; std::string stdout_str; std::string stderr_str; + + explicit ProcResult(int status) : status(status) {} + ProcResult(ProcResult&&) noexcept = default; + ProcResult& operator=(ProcResult&&) noexcept = default; + + explicit operator bool() const { return status >= 0; } }; -// Fork, exec and wait for an external process. Return nullptr if the process could not be launched, -// otherwise a ProcResult containing the external process' exit status and captured stdout and -// stderr. -std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv); +// Fork, exec and wait for an external process. Returns status < 0 if the process could not be +// launched, otherwise a ProcResult containing the external process' exit status and captured +// stdout and stderr. +ProcResult ExecuteBinary(const std::vector<std::string>& argv); } // namespace util } // namespace android diff --git a/libs/androidfw/include/androidfw/ResourceTimer.h b/libs/androidfw/include/androidfw/ResourceTimer.h new file mode 100644 index 000000000000..74613519a920 --- /dev/null +++ b/libs/androidfw/include/androidfw/ResourceTimer.h @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2022 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. + */ + +#ifndef ANDROIDFW_RESOURCETIMER_H_ +#define ANDROIDFW_RESOURCETIMER_H_ + +#include <time.h> +#include <atomic> +#include <vector> + +#include <utils/Mutex.h> +#include <android-base/macros.h> +#include <androidfw/Util.h> + +namespace android { + +// ResourceTimer captures the duration of short functions. Durations are accumulated in registers +// and statistics are pulled back to the Java layer as needed. +// To monitor an API, first add it to the Counter enumeration. Then, inside the API, create an +// instance of ResourceTimer with the appropriate enumeral. The corresponding counter will be +// updated when the ResourceTimer destructor is called, normally at the end of the enclosing block. +class ResourceTimer { + public: + enum class Counter { + GetResourceValue, + RetrieveAttributes, + + LastCounter = RetrieveAttributes, + }; + static const int counterSize = static_cast<int>(Counter::LastCounter) + 1; + static char const *toString(Counter); + + // Start a timer for the specified counter. + ResourceTimer(Counter); + // The block is exiting. If the timer is active, record it. + ~ResourceTimer(); + // This records the elapsed time and disables further recording. Use this if the containing + // block includes extra processing that should not be included in the timer. The method is + // destructive in that the timer is no longer valid and further calls to record() will be + // ignored. + void record(); + // This cancels a timer. Elapsed time will neither be computed nor recorded. + void cancel(); + + // A single timer contains the count of events and the cumulative time spent handling the + // events. It also includes the smallest value seen and 10 largest values seen. Finally, it + // includes a histogram of values that approximates a semi-log. + + // The timer can compute percentiles of recorded events. For example, the p50 value is a time + // such that 50% of the readings are below the value and 50% are above the value. The + // granularity in the readings means that a percentile cannot always be computed. In this case, + // the percentile is reported as zero. (The simplest example is when there is a single + // reading.) Even if the value can be computed, it will not be exact. Therefore, a percentile + // is actually reported as two values: the lowest time at which it might be valid and the + // highest time at which it might be valid. + struct Timer { + static const size_t MaxLargest = 5; + + // The construct zeros all the fields. The destructor releases memory allocated to the + // buckets. + Timer(); + ~Timer(); + + // The following summary values are set to zero on a reset. All times are in ns. + + // The total number of events recorded. + int count; + // The total duration of events. + int64_t total; + // The smallest event duration seen. This is guaranteed to be non-zero if count is greater + // than 0. + int mintime; + // The largest event duration seen. + int maxtime; + + // The largest values seen. Element 0 is the largest value seen (and is the same as maxtime, + // above). Element 1 is the next largest, and so on. If count is less than MaxLargest, + // unused elements will be zero. + int largest[MaxLargest]; + + // The p50 value is a time such that 50% of the readings are below that time and 50% of the + // readings. + + // A single percentile is defined by the lowest value supported by the readings and the + // highest value supported by the readings. + struct Percentile { + // The nominal time (in ns) of the percentile. The true percentile is guaranteed to be less + // than or equal to this time. + int nominal; + // The actual percentile of the nominal time. + int nominal_actual; + // The time of the next lower bin. The true percentile is guaranteed to be greater than + // this time. + int floor; + // The actual percentile of the floor time. + int floor_actual; + + // Fill in a percentile given the cumulative to the bin, the count in the current bin, the + // total count, the width of the bin, and the time of the bin. + void compute(int cumulative, int current, int count, int width, int time); + }; + + // The structure that holds the percentiles. + struct { + Percentile p50; + Percentile p90; + Percentile p95; + Percentile p99; + } pvalues; + + // Set all counters to zero. + void reset(); + // Record an event. The input time is in ns. + void record(int); + // Compute the percentiles. Percentiles are computed on demand, as the computation is too + // expensive to be done inline. + void compute(); + + // Copy one timer to another. If reset is true then the src is reset immediately after the + // copy. The reset flag is exploited to make the copy faster. Any data in dst is lost. + static void copy(Timer &dst, Timer &src, bool reset); + + private: + // Free any buckets. + void freeBuckets(); + + // Readings are placed in bins, which are orgzanized into decades. The decade 0 covers + // [0,100) in steps of 1us. Decade 1 covers [0,1000) in steps of 10us. Decade 2 covers + // [0,10000) in steps of 100us. And so on. + + // An event is placed in the first bin that can hold it. This means that events in the range + // of [0,100) are placed in the first decade, events in the range of [0,1000) are placed in + // the second decade, and so on. This also means that the first 10% of the bins are unused + // in each decade after the first. + + // The design provides at least two significant digits across the range of [0,10000). + + static const size_t MaxDimension = 4; + static const size_t MaxBuckets = 100; + + // The range of each dimension. The lower value is always zero. + static const int range[MaxDimension]; + // The width of each bin, by dimension + static const int width[MaxDimension]; + + // A histogram of the values seen. Centuries are allocated as needed, to minimize the memory + // impact. + int *buckets[MaxDimension]; + }; + + // Fetch one Timer. The function has a short-circuit behavior: if the count is zero then + // destination count is set to zero and the function returns false. Otherwise, the destination + // is a copy of the source and the function returns true. This behavior lowers the cost of + // handling unused timers. + static bool copy(int src, Timer &dst, bool reset); + + // Enable the timers. Timers are initially disabled. Enabling timers allocates memory for the + // counters. Timers cannot be disabled. + static void enable(); + + private: + // An internal reset method. This does not take a lock. + static void reset(); + + // Helper method to convert a counter into an enum. Presumably, this will be inlined into zero + // actual cpu instructions. + static inline std::vector<unsigned int>::size_type toIndex(Counter c) { + return static_cast<std::vector<unsigned int>::size_type>(c); + } + + // Every counter has an associated lock. The lock has been factored into a separate class to + // keep the Timer class a POD. + struct GuardedTimer { + Mutex lock_; + Timer timer_; + }; + + // Scoped timer + struct ScopedTimer { + AutoMutex _l; + Timer &t; + ScopedTimer(GuardedTimer &g) : + _l(g.lock_), t(g.timer_) { + } + Timer *operator->() { + return &t; + } + Timer& operator*() { + return t; + } + }; + + // An individual timer is active (or not), is tracking a specific API, and has a start time. + // The api and the start time are undefined if the timer is not active. + bool active_; + Counter api_; + struct timespec start_; + + // The global enable flag. This is initially false and may be set true by the java runtime. + static std::atomic<bool> enabled_; + + // The global timers. The memory for the timers is not allocated until the timers are enabled. + static std::atomic<GuardedTimer *> counter_; +}; + +} // namespace android + +#endif /* ANDROIDFW_RESOURCETIMER_H_ */ diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 3d66244646d5..52321dad8a5a 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -21,11 +21,13 @@ #define _LIBS_UTILS_RESOURCE_TYPES_H #include <android-base/expected.h> +#include <android-base/unique_fd.h> #include <androidfw/Asset.h> #include <androidfw/Errors.h> #include <androidfw/LocaleData.h> #include <androidfw/StringPiece.h> +#include <utils/ByteOrder.h> #include <utils/Errors.h> #include <utils/String16.h> #include <utils/Vector.h> @@ -45,7 +47,7 @@ namespace android { constexpr const uint32_t kIdmapMagic = 0x504D4449u; -constexpr const uint32_t kIdmapCurrentVersion = 0x00000008u; +constexpr const uint32_t kIdmapCurrentVersion = 0x00000009u; // This must never change. constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian) @@ -53,10 +55,12 @@ constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endi // The version should only be changed when a backwards-incompatible change must be made to the // fabricated overlay file format. Old fabricated overlays must be migrated to the new file format // to prevent losing fabricated overlay data. -constexpr const uint32_t kFabricatedOverlayCurrentVersion = 1; +constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3; // Returns whether or not the path represents a fabricated overlay. bool IsFabricatedOverlay(const std::string& path); +bool IsFabricatedOverlay(const char* path); +bool IsFabricatedOverlay(android::base::borrowed_fd fd); /** * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of @@ -1098,7 +1102,7 @@ struct ResTable_config SDKVERSION_ANY = 0 }; - enum { + enum { MINORVERSION_ANY = 0 }; @@ -1437,6 +1441,10 @@ struct ResTable_type // Mark any types that use this with a v26 qualifier to prevent runtime issues on older // platforms. FLAG_SPARSE = 0x01, + + // If set, the offsets to the entries are encoded in 16-bit, real_offset = offset * 4u + // An 16-bit offset of 0xffffu means a NO_ENTRY + FLAG_OFFSET16 = 0x02, }; uint8_t flags; @@ -1453,6 +1461,11 @@ struct ResTable_type ResTable_config config; }; +// Convert a 16-bit offset to 32-bit if FLAG_OFFSET16 is set +static inline uint32_t offset_from16(uint16_t off16) { + return dtohs(off16) == 0xffffu ? ResTable_type::NO_ENTRY : dtohs(off16) * 4u; +} + // The minimum size required to read any version of ResTable_type. constexpr size_t kResTableTypeMinSize = sizeof(ResTable_type) - sizeof(ResTable_config) + sizeof(ResTable_config::size); @@ -1480,6 +1493,8 @@ union ResTable_sparseTypeEntry { static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t), "ResTable_sparseTypeEntry must be 4 bytes in size"); +struct ResTable_map_entry; + /** * This is the beginning of information about an entry in the resource * table. It holds the reference to the name of this entry, and is @@ -1487,12 +1502,11 @@ static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t), * * A Res_value structure, if FLAG_COMPLEX is -not- set. * * An array of ResTable_map structures, if FLAG_COMPLEX is set. * These supply a set of name/value mappings of data. + * * If FLAG_COMPACT is set, this entry is a compact entry for + * simple values only */ -struct ResTable_entry +union ResTable_entry { - // Number of bytes in this structure. - uint16_t size; - enum { // If set, this is a complex entry, holding a set of name/value // mappings. It is followed by an array of ResTable_map structures. @@ -1504,18 +1518,91 @@ struct ResTable_entry // resources of the same name/type. This is only useful during // linking with other resource tables. FLAG_WEAK = 0x0004, + // If set, this is a compact entry with data type and value directly + // encoded in the this entry, see ResTable_entry::compact + FLAG_COMPACT = 0x0008, }; - uint16_t flags; - - // Reference into ResTable_package::keyStrings identifying this entry. - struct ResStringPool_ref key; + + struct Full { + // Number of bytes in this structure. + uint16_t size; + + uint16_t flags; + + // Reference into ResTable_package::keyStrings identifying this entry. + struct ResStringPool_ref key; + } full; + + /* A compact entry is indicated by FLAG_COMPACT, with flags at the same + * offset as a normal entry. This is only for simple data values where + * + * - size for entry or value can be inferred (both being 8 bytes). + * - key index is encoded in 16-bit + * - dataType is encoded as the higher 8-bit of flags + * - data is encoded directly in this entry + */ + struct Compact { + uint16_t key; + uint16_t flags; + uint32_t data; + } compact; + + uint16_t flags() const { return dtohs(full.flags); }; + bool is_compact() const { return flags() & FLAG_COMPACT; } + bool is_complex() const { return flags() & FLAG_COMPLEX; } + + size_t size() const { + return is_compact() ? sizeof(ResTable_entry) : dtohs(this->full.size); + } + + uint32_t key() const { + return is_compact() ? dtohs(this->compact.key) : dtohl(this->full.key.index); + } + + /* Always verify the memory associated with this entry and its value + * before calling value() or map_entry() + */ + Res_value value() const { + Res_value v; + if (is_compact()) { + v.size = sizeof(Res_value); + v.res0 = 0; + v.data = dtohl(this->compact.data); + v.dataType = dtohs(compact.flags) >> 8; + } else { + auto vaddr = reinterpret_cast<const uint8_t*>(this) + dtohs(this->full.size); + auto value = reinterpret_cast<const Res_value*>(vaddr); + v.size = dtohs(value->size); + v.res0 = value->res0; + v.data = dtohl(value->data); + v.dataType = value->dataType; + } + return v; + } + + const ResTable_map_entry* map_entry() const { + return is_complex() && !is_compact() ? + reinterpret_cast<const ResTable_map_entry*>(this) : nullptr; + } }; +/* Make sure size of ResTable_entry::Full and ResTable_entry::Compact + * be the same as ResTable_entry. This is to allow iteration of entries + * to work in either cases. + * + * The offset of flags must be at the same place for both structures, + * to ensure the correct reading to decide whether this is a full entry + * or a compact entry. + */ +static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Full)); +static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Compact)); +static_assert(offsetof(ResTable_entry, full.flags) == offsetof(ResTable_entry, compact.flags)); + /** * Extended form of a ResTable_entry for map entries, defining a parent map * resource from which to inherit values. */ -struct ResTable_map_entry : public ResTable_entry +struct ResTable_map_entry : public ResTable_entry::Full { // Resource identifier of the parent mapping, or 0 if there is none. // This is always treated as a TYPE_DYNAMIC_REFERENCE. @@ -1746,6 +1833,28 @@ inline ResTable_overlayable_policy_header::PolicyFlags& operator |=( return first; } +using ResourceId = uint32_t; // 0xpptteeee + +using DataType = uint8_t; // Res_value::dataType +using DataValue = uint32_t; // Res_value::data + +struct OverlayManifestInfo { + std::string package_name; // NOLINT(misc-non-private-member-variables-in-classes) + std::string name; // NOLINT(misc-non-private-member-variables-in-classes) + std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes) + std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes) + ResourceId resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes) +}; + +struct FabricatedOverlayEntryParameters { + std::string resource_name; + DataType data_type; + DataValue data_value; + std::string data_string_value; + std::optional<android::base::borrowed_fd> data_binary_value; + std::string configuration; +}; + class AssetManager2; /** @@ -1776,7 +1885,10 @@ public: void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId); - void addAlias(uint32_t stagedId, uint32_t finalizedId); + using AliasMap = std::vector<std::pair<uint32_t, uint32_t>>; + void setAliases(AliasMap aliases) { + mAliasId = std::move(aliases); + } // Returns whether or not the value must be looked up. bool requiresLookup(const Res_value* value) const; @@ -1790,12 +1902,12 @@ public: return mEntries; } -private: - uint8_t mAssignedPackageId; - uint8_t mLookupTable[256]; - KeyedVector<String16, uint8_t> mEntries; - bool mAppAsLib; - std::map<uint32_t, uint32_t> mAliasId; + private: + uint8_t mLookupTable[256]; + uint8_t mAssignedPackageId; + bool mAppAsLib; + KeyedVector<String16, uint8_t> mEntries; + AliasMap mAliasId; }; bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue); diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h index bd1c44033b88..2d90a526dfbe 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -25,14 +25,14 @@ namespace android { // Extracts the package, type, and name from a string of the format: [[package:]type/]name // Validation must be performed on each extracted piece. // Returns false if there was a syntax error. -bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type, +bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type, StringPiece* out_entry); // Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName. // Useful for getting resource name without re-running AssetManager2::FindEntry searches. base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName( const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref, - const StringPiece& package_name); + StringPiece package_name); // Formats a ResourceName to "package:type/entry_name". std::string ToFormattedResourceString(const AssetManager2::ResourceName& resource_name); diff --git a/libs/androidfw/include/androidfw/Source.h b/libs/androidfw/include/androidfw/Source.h new file mode 100644 index 000000000000..ddc9ba421101 --- /dev/null +++ b/libs/androidfw/include/androidfw/Source.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef _ANDROID_SOURCE_H +#define _ANDROID_SOURCE_H + +#include <optional> +#include <ostream> +#include <string> + +#include "android-base/stringprintf.h" +#include "androidfw/StringPiece.h" + +namespace android { + +// Represents a file on disk. Used for logging and showing errors. +struct Source { + std::string path; + std::optional<size_t> line; + std::optional<std::string> archive; + + Source() = default; + + inline Source(android::StringPiece path) : path(path) { // NOLINT(implicit) + } + + inline Source(android::StringPiece path, android::StringPiece archive) + : path(path), archive(archive) { + } + + inline Source(android::StringPiece path, size_t line) : path(path), line(line) { + } + + inline Source WithLine(size_t line) const { + return Source(path, line); + } + + std::string to_string() const { + std::string s = path; + if (archive) { + s = ::android::base::StringPrintf("%s@%s", archive.value().c_str(), s.c_str()); + } + if (line) { + s = ::android::base::StringPrintf("%s:%zd", s.c_str(), line.value()); + } + return s; + } +}; + +// +// Implementations +// + +inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) { + return out << source.to_string(); +} + +inline bool operator==(const Source& lhs, const Source& rhs) { + return lhs.path == rhs.path && lhs.line == rhs.line; +} + +inline bool operator<(const Source& lhs, const Source& rhs) { + int cmp = lhs.path.compare(rhs.path); + if (cmp < 0) return true; + if (cmp > 0) return false; + if (lhs.line) { + if (rhs.line) { + return lhs.line.value() < rhs.line.value(); + } + return false; + } + return bool(rhs.line); +} + +} // namespace android + +#endif // _ANDROID_SOURCE_H diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h index 921877dc4982..f6cc64edfb5a 100644 --- a/libs/androidfw/include/androidfw/StringPiece.h +++ b/libs/androidfw/include/androidfw/StringPiece.h @@ -19,307 +19,37 @@ #include <ostream> #include <string> +#include <string_view> -#include "utils/JenkinsHash.h" #include "utils/Unicode.h" namespace android { -// Read only wrapper around basic C strings. Prevents excessive copying. -// StringPiece does not own the data it is wrapping. The lifetime of the underlying -// data must outlive this StringPiece. -// -// WARNING: When creating from std::basic_string<>, moving the original -// std::basic_string<> will invalidate the data held in a BasicStringPiece<>. -// BasicStringPiece<> should only be used transitively. -// -// NOTE: When creating an std::pair<StringPiece, T> using std::make_pair(), -// passing an std::string will first copy the string, then create a StringPiece -// on the copy, which is then immediately destroyed. -// Instead, create a StringPiece explicitly: -// -// std::string my_string = "foo"; -// std::make_pair<StringPiece, T>(StringPiece(my_string), ...); -template <typename TChar> -class BasicStringPiece { - public: - using const_iterator = const TChar*; - using difference_type = size_t; - using size_type = size_t; - - // End of string marker. - constexpr static const size_t npos = static_cast<size_t>(-1); - - BasicStringPiece(); - BasicStringPiece(const BasicStringPiece<TChar>& str); - BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(google-explicit-constructor) - BasicStringPiece(const TChar* str); // NOLINT(google-explicit-constructor) - BasicStringPiece(const TChar* str, size_t len); - - BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs); - BasicStringPiece<TChar>& assign(const TChar* str, size_t len); - - BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const; - BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin, - BasicStringPiece<TChar>::const_iterator end) const; - - const TChar* data() const; - size_t length() const; - size_t size() const; - bool empty() const; - std::basic_string<TChar> to_string() const; - - bool contains(const BasicStringPiece<TChar>& rhs) const; - int compare(const BasicStringPiece<TChar>& rhs) const; - bool operator<(const BasicStringPiece<TChar>& rhs) const; - bool operator>(const BasicStringPiece<TChar>& rhs) const; - bool operator==(const BasicStringPiece<TChar>& rhs) const; - bool operator!=(const BasicStringPiece<TChar>& rhs) const; - - const_iterator begin() const; - const_iterator end() const; - - private: - const TChar* data_; - size_t length_; -}; +template <class T> +using BasicStringPiece = std::basic_string_view<T>; using StringPiece = BasicStringPiece<char>; using StringPiece16 = BasicStringPiece<char16_t>; -// -// BasicStringPiece implementation. -// - -template <typename TChar> -constexpr const size_t BasicStringPiece<TChar>::npos; - -template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece() : data_(nullptr), length_(0) {} - -template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str) - : data_(str.data_), length_(str.length_) {} - -template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str) - : data_(str.data()), length_(str.length()) {} - -template <> -inline BasicStringPiece<char>::BasicStringPiece(const char* str) - : data_(str), length_(str != nullptr ? strlen(str) : 0) {} - -template <> -inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) - : data_(str), length_(str != nullptr ? strlen16(str) : 0) {} - -template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) - : data_(str), length_(len) {} - -template <typename TChar> -inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=( - const BasicStringPiece<TChar>& rhs) { - data_ = rhs.data_; - length_ = rhs.length_; - return *this; -} - -template <typename TChar> -inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) { - data_ = str; - length_ = len; - return *this; -} - -template <typename TChar> -inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const { - if (len == npos) { - len = length_ - start; - } - - if (start > length_ || start + len > length_) { - return BasicStringPiece<TChar>(); - } - return BasicStringPiece<TChar>(data_ + start, len); -} - -template <typename TChar> -inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr( - BasicStringPiece<TChar>::const_iterator begin, - BasicStringPiece<TChar>::const_iterator end) const { - return BasicStringPiece<TChar>(begin, end - begin); -} - -template <typename TChar> -inline const TChar* BasicStringPiece<TChar>::data() const { - return data_; -} - -template <typename TChar> -inline size_t BasicStringPiece<TChar>::length() const { - return length_; -} - -template <typename TChar> -inline size_t BasicStringPiece<TChar>::size() const { - return length_; -} - -template <typename TChar> -inline bool BasicStringPiece<TChar>::empty() const { - return length_ == 0; -} - -template <typename TChar> -inline std::basic_string<TChar> BasicStringPiece<TChar>::to_string() const { - return std::basic_string<TChar>(data_, length_); -} - -template <> -inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const { - if (!data_ || !rhs.data_) { - return false; - } - if (rhs.length_ > length_) { - return false; - } - return strstr(data_, rhs.data_) != nullptr; -} - -template <> -inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const { - const char nullStr = '\0'; - const char* b1 = data_ != nullptr ? data_ : &nullStr; - const char* e1 = b1 + length_; - const char* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr; - const char* e2 = b2 + rhs.length_; - - while (b1 < e1 && b2 < e2) { - const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++); - if (d) { - return d; - } - } - return static_cast<int>(length_ - rhs.length_); -} - -inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) { - const ssize_t result_len = utf16_to_utf8_length(str.data(), str.size()); - if (result_len < 0) { - // Empty string. - return out; - } - - std::string result; - result.resize(static_cast<size_t>(result_len)); - utf16_to_utf8(str.data(), str.length(), &*result.begin(), static_cast<size_t>(result_len) + 1); - return out << result; -} - -template <> -inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const { - if (!data_ || !rhs.data_) { - return false; - } - if (rhs.length_ > length_) { - return false; - } - return strstr16(data_, rhs.data_) != nullptr; -} - -template <> -inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const { - const char16_t nullStr = u'\0'; - const char16_t* b1 = data_ != nullptr ? data_ : &nullStr; - const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr; - return strzcmp16(b1, length_, b2, rhs.length_); -} - -template <typename TChar> -inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) < 0; -} - -template <typename TChar> -inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) > 0; -} - -template <typename TChar> -inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) == 0; -} - -template <typename TChar> -inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) != 0; -} - -template <typename TChar> -inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const { - return data_; -} - -template <typename TChar> -inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const { - return data_ + length_; -} - -template <typename TChar> -inline bool operator==(const TChar* lhs, const BasicStringPiece<TChar>& rhs) { - return BasicStringPiece<TChar>(lhs) == rhs; -} - -template <typename TChar> -inline bool operator!=(const TChar* lhs, const BasicStringPiece<TChar>& rhs) { - return BasicStringPiece<TChar>(lhs) != rhs; -} - -inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) { - return out.write(str.data(), str.size()); -} - -template <typename TChar> -inline ::std::basic_string<TChar>& operator+=(::std::basic_string<TChar>& lhs, - const BasicStringPiece<TChar>& rhs) { - return lhs.append(rhs.data(), rhs.size()); -} - -template <typename TChar> -inline bool operator==(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) { - return rhs == lhs; -} - -template <typename TChar> -inline bool operator!=(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) { - return rhs != lhs; -} - } // namespace android -inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) { +namespace std { + +inline ::std::ostream& operator<<(::std::ostream& out, ::std::u16string_view str) { ssize_t utf8_len = utf16_to_utf8_length(str.data(), str.size()); if (utf8_len < 0) { - return out << "???"; + return out; // empty } std::string utf8; utf8.resize(static_cast<size_t>(utf8_len)); - utf16_to_utf8(str.data(), str.size(), &*utf8.begin(), utf8_len + 1); + utf16_to_utf8(str.data(), str.size(), utf8.data(), utf8_len + 1); return out << utf8; } -namespace std { - -template <typename TChar> -struct hash<android::BasicStringPiece<TChar>> { - size_t operator()(const android::BasicStringPiece<TChar>& str) const { - uint32_t hashCode = android::JenkinsHashMixBytes( - 0, reinterpret_cast<const uint8_t*>(str.data()), sizeof(TChar) * str.size()); - return static_cast<size_t>(hashCode); - } -}; +inline ::std::ostream& operator<<(::std::ostream& out, const ::std::u16string& str) { + return out << std::u16string_view(str); +} } // namespace std diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h new file mode 100644 index 000000000000..0190ab57bf23 --- /dev/null +++ b/libs/androidfw/include/androidfw/StringPool.h @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef _ANDROID_STRING_POOL_H +#define _ANDROID_STRING_POOL_H + +#include <functional> +#include <memory> +#include <string> +#include <unordered_map> +#include <vector> + +#include "BigBuffer.h" +#include "IDiagnostics.h" +#include "android-base/macros.h" +#include "androidfw/ConfigDescription.h" +#include "androidfw/StringPiece.h" + +namespace android { + +struct Span { + std::string name; + uint32_t first_char; + uint32_t last_char; + + bool operator==(const Span& right) const { + return name == right.name && first_char == right.first_char && last_char == right.last_char; + } +}; + +struct StyleString { + std::string str; + std::vector<Span> spans; +}; + +// A StringPool for storing the value of String and StyledString resources. +// Styles and Strings are stored separately, since the runtime variant of this +// class -- ResStringPool -- requires that styled strings *always* appear first, since their +// style data is stored as an array indexed by the same indices as the main string pool array. +// Otherwise, the style data array would have to be sparse and take up more space. +class StringPool { + public: + using size_type = size_t; + + class Context { + public: + enum : uint32_t { + kHighPriority = 1u, + kNormalPriority = 0x7fffffffu, + kLowPriority = 0xffffffffu, + }; + uint32_t priority = kNormalPriority; + android::ConfigDescription config; + + Context() = default; + Context(uint32_t p, const android::ConfigDescription& c) : priority(p), config(c) { + } + explicit Context(uint32_t p) : priority(p) { + } + explicit Context(const android::ConfigDescription& c) : priority(kNormalPriority), config(c) { + } + }; + + class Entry; + + class Ref { + public: + Ref(); + Ref(const Ref&); + ~Ref(); + + Ref& operator=(const Ref& rhs); + bool operator==(const Ref& rhs) const; + bool operator!=(const Ref& rhs) const; + const std::string* operator->() const; + const std::string& operator*() const; + + size_t index() const; + const Context& GetContext() const; + + private: + friend class StringPool; + + explicit Ref(Entry* entry); + + Entry* entry_; + }; + + class StyleEntry; + + class StyleRef { + public: + StyleRef(); + StyleRef(const StyleRef&); + ~StyleRef(); + + StyleRef& operator=(const StyleRef& rhs); + bool operator==(const StyleRef& rhs) const; + bool operator!=(const StyleRef& rhs) const; + const StyleEntry* operator->() const; + const StyleEntry& operator*() const; + + size_t index() const; + const Context& GetContext() const; + + private: + friend class StringPool; + + explicit StyleRef(StyleEntry* entry); + + StyleEntry* entry_; + }; + + class Entry { + public: + std::string value; + Context context; + + private: + friend class StringPool; + friend class Ref; + + size_t index_; + int ref_; + const StringPool* pool_; + }; + + struct Span { + Ref name; + uint32_t first_char; + uint32_t last_char; + }; + + class StyleEntry { + public: + std::string value; + Context context; + std::vector<Span> spans; + + private: + friend class StringPool; + friend class StyleRef; + + size_t index_; + int ref_; + }; + + static bool FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag); + static bool FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag); + + StringPool() = default; + StringPool(StringPool&&) = default; + StringPool& operator=(StringPool&&) = default; + + // Adds a string to the pool, unless it already exists. Returns a reference to the string in the + // pool. + Ref MakeRef(android::StringPiece str); + + // Adds a string to the pool, unless it already exists, with a context object that can be used + // when sorting the string pool. Returns a reference to the string in the pool. + Ref MakeRef(android::StringPiece str, const Context& context); + + // Adds a string from another string pool. Returns a reference to the string in the string pool. + Ref MakeRef(const Ref& ref); + + // Adds a style to the string pool and returns a reference to it. + StyleRef MakeRef(const StyleString& str); + + // Adds a style to the string pool with a context object that can be used when sorting the string + // pool. Returns a reference to the style in the string pool. + StyleRef MakeRef(const StyleString& str, const Context& context); + + // Adds a style from another string pool. Returns a reference to the style in the string pool. + StyleRef MakeRef(const StyleRef& ref); + + // Moves pool into this one without coalescing strings. When this function returns, pool will be + // empty. + void Merge(StringPool&& pool); + + inline const std::vector<std::unique_ptr<Entry>>& strings() const { + return strings_; + } + + // Returns the number of strings in the table. + inline size_t size() const { + return styles_.size() + strings_.size(); + } + + // Reserves space for strings and styles as an optimization. + void HintWillAdd(size_t string_count, size_t style_count); + + // Sorts the strings according to their Context using some comparison function. + // Equal Contexts are further sorted by string value, lexicographically. + // If no comparison function is provided, values are only sorted lexicographically. + void Sort(const std::function<int(const Context&, const Context&)>& cmp = nullptr); + + // Removes any strings that have no references. + void Prune(); + + private: + DISALLOW_COPY_AND_ASSIGN(StringPool); + + static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag); + + Ref MakeRefImpl(android::StringPiece str, const Context& context, bool unique); + void ReAssignIndices(); + + std::vector<std::unique_ptr<Entry>> strings_; + std::vector<std::unique_ptr<StyleEntry>> styles_; + std::unordered_multimap<android::StringPiece, Entry*> indexed_strings_; +}; + +} // namespace android + +#endif // _ANDROID_STRING_POOL_H diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h index c59b5b6c51a2..a188abb7ecb5 100644 --- a/libs/androidfw/include/androidfw/Util.h +++ b/libs/androidfw/include/androidfw/Util.h @@ -17,20 +17,29 @@ #ifndef UTIL_H_ #define UTIL_H_ +#include <android-base/macros.h> +#include <util/map_ptr.h> + #include <cstdlib> #include <memory> -#include <sstream> #include <vector> -#include <android-base/macros.h> -#include <util/map_ptr.h> - +#include "androidfw/BigBuffer.h" +#include "androidfw/ResourceTypes.h" #include "androidfw/StringPiece.h" +#include "utils/ByteOrder.h" #ifdef __ANDROID__ #define ANDROID_LOG(x) LOG(x) #else -#define ANDROID_LOG(x) std::stringstream() +namespace android { +// No default logging for aapt2, as it's too noisy for a command line dev tool. +struct NullLogger { + template <class T> + friend const NullLogger& operator<<(const NullLogger& l, const T&) { return l; } +}; +} +#define ANDROID_LOG(x) (android::NullLogger{}) #endif namespace android { @@ -46,95 +55,67 @@ std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T{std::forward<Args>(args)...}); } -// Based on std::unique_ptr, but uses free() to release malloc'ed memory -// without incurring the size increase of holding on to a custom deleter. -template <typename T> -class unique_cptr { - public: - using pointer = typename std::add_pointer<T>::type; - - constexpr unique_cptr() : ptr_(nullptr) {} - constexpr explicit unique_cptr(std::nullptr_t) : ptr_(nullptr) {} - explicit unique_cptr(pointer ptr) : ptr_(ptr) {} - unique_cptr(unique_cptr&& o) noexcept : ptr_(o.ptr_) { o.ptr_ = nullptr; } - - ~unique_cptr() { std::free(reinterpret_cast<void*>(ptr_)); } - - inline unique_cptr& operator=(unique_cptr&& o) noexcept { - if (&o == this) { - return *this; - } - - std::free(reinterpret_cast<void*>(ptr_)); - ptr_ = o.ptr_; - o.ptr_ = nullptr; - return *this; - } - - inline unique_cptr& operator=(std::nullptr_t) { - std::free(reinterpret_cast<void*>(ptr_)); - ptr_ = nullptr; - return *this; - } - - pointer release() { - pointer result = ptr_; - ptr_ = nullptr; - return result; - } - - inline pointer get() const { return ptr_; } - - void reset(pointer ptr = pointer()) { - if (ptr == ptr_) { - return; - } - - pointer old_ptr = ptr_; - ptr_ = ptr; - std::free(reinterpret_cast<void*>(old_ptr)); +// Based on std::unique_ptr, but uses free() to release malloc'ed memory. +struct FreeDeleter { + void operator()(void* ptr) const { + ::free(ptr); } +}; +template <typename T> +using unique_cptr = std::unique_ptr<T, FreeDeleter>; - inline void swap(unique_cptr& o) { std::swap(ptr_, o.ptr_); } - - inline explicit operator bool() const { return ptr_ != nullptr; } - - inline typename std::add_lvalue_reference<T>::type operator*() const { return *ptr_; } - - inline pointer operator->() const { return ptr_; } +void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out); - inline bool operator==(const unique_cptr& o) const { return ptr_ == o.ptr_; } +// Converts a UTF-8 string to a UTF-16 string. +std::u16string Utf8ToUtf16(StringPiece utf8); - inline bool operator!=(const unique_cptr& o) const { return ptr_ != o.ptr_; } +// Converts a UTF-16 string to a UTF-8 string. +std::string Utf16ToUtf8(StringPiece16 utf16); - inline bool operator==(std::nullptr_t) const { return ptr_ == nullptr; } +// Converts a UTF8 string into Modified UTF8 +std::string Utf8ToModifiedUtf8(std::string_view utf8); - inline bool operator!=(std::nullptr_t) const { return ptr_ != nullptr; } +// Converts a Modified UTF8 string into a UTF8 string +std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8); - private: - DISALLOW_COPY_AND_ASSIGN(unique_cptr); +inline uint16_t HostToDevice16(uint16_t value) { + return htods(value); +} - pointer ptr_; -}; +inline uint32_t HostToDevice32(uint32_t value) { + return htodl(value); +} -void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out); +inline uint16_t DeviceToHost16(uint16_t value) { + return dtohs(value); +} -// Converts a UTF-8 string to a UTF-16 string. -std::u16string Utf8ToUtf16(const StringPiece& utf8); +inline uint32_t DeviceToHost32(uint32_t value) { + return dtohl(value); +} -// Converts a UTF-16 string to a UTF-8 string. -std::string Utf16ToUtf8(const StringPiece16& utf16); +std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep); -std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep); +inline bool IsFourByteAligned(const void* data) { + return ((uintptr_t)data & 0x3U) == 0; +} template <typename T> inline bool IsFourByteAligned(const incfs::map_ptr<T>& data) { - return ((size_t)data.unsafe_ptr() & 0x3U) == 0; + return IsFourByteAligned(data.unsafe_ptr()); } -inline bool IsFourByteAligned(const void* data) { - return ((size_t)data & 0x3U) == 0; -} +// Helper method to extract a UTF-16 string from a StringPool. If the string is stored as UTF-8, +// the conversion to UTF-16 happens within ResStringPool. +android::StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx); + +// Helper method to extract a UTF-8 string from a StringPool. If the string is stored as UTF-16, +// the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is done by this method, +// which maintains no state or cache. This means we must return an std::string copy. +std::string GetString(const android::ResStringPool& pool, size_t idx); + +// Copies the entire BigBuffer into a single buffer. +std::unique_ptr<uint8_t[]> Copy(const android::BigBuffer& buffer); } // namespace util } // namespace android diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h index 5a5a0e29125d..d40d24ede769 100644 --- a/libs/androidfw/include/androidfw/misc.h +++ b/libs/androidfw/include/androidfw/misc.h @@ -44,6 +44,10 @@ FileType getFileType(const char* fileName); /* get the file's modification date; returns -1 w/errno set on failure */ time_t getFileModDate(const char* fileName); +// Check if |path| or |fd| resides on a readonly filesystem. +bool isReadonlyFilesystem(const char* path); +bool isReadonlyFilesystem(int fd); + }; // namespace android #endif // _LIBS_ANDROID_FW_MISC_H diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp index 52854205207c..d3949e9cf69f 100644 --- a/libs/androidfw/misc.cpp +++ b/libs/androidfw/misc.cpp @@ -21,12 +21,17 @@ // #include <androidfw/misc.h> -#include <sys/stat.h> +#include "android-base/logging.h" + +#ifdef __linux__ +#include <sys/statvfs.h> +#include <sys/vfs.h> +#endif // __linux__ + #include <cstring> -#include <errno.h> #include <cstdio> - -using namespace android; +#include <errno.h> +#include <sys/stat.h> namespace android { @@ -41,8 +46,7 @@ FileType getFileType(const char* fileName) if (errno == ENOENT || errno == ENOTDIR) return kFileTypeNonexistent; else { - fprintf(stderr, "getFileType got errno=%d on '%s'\n", - errno, fileName); + PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed"; return kFileTypeUnknown; } } else { @@ -82,4 +86,32 @@ time_t getFileModDate(const char* fileName) return sb.st_mtime; } +#ifndef __linux__ +// No need to implement these on the host, the functions only matter on a device. +bool isReadonlyFilesystem(const char*) { + return false; +} +bool isReadonlyFilesystem(int) { + return false; +} +#else // __linux__ +bool isReadonlyFilesystem(const char* path) { + struct statfs sfs; + if (::statfs(path, &sfs)) { + PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed"; + return false; + } + return (sfs.f_flags & ST_RDONLY) != 0; +} + +bool isReadonlyFilesystem(int fd) { + struct statfs sfs; + if (::fstatfs(fd, &sfs)) { + PLOG(ERROR) << "isReadonlyFilesystem(): fstatfs(" << fd << ") failed"; + return false; + } + return (sfs.f_flags & ST_RDONLY) != 0; +} +#endif // __linux__ + }; // namespace android diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp index ddd8ab820cb1..1c89c61c8f78 100644 --- a/libs/androidfw/tests/AttributeResolution_bench.cpp +++ b/libs/androidfw/tests/AttributeResolution_bench.cpp @@ -120,8 +120,8 @@ static void BM_ApplyStyleFramework(benchmark::State& state) { return; } - std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset(layout_path->to_string(), value->cookie, - Asset::ACCESS_BUFFER); + std::unique_ptr<Asset> asset = + assetmanager.OpenNonAsset(std::string(*layout_path), value->cookie, Asset::ACCESS_BUFFER); if (asset == nullptr) { state.SkipWithError("failed to load layout"); return; diff --git a/libs/androidfw/tests/BigBuffer_test.cpp b/libs/androidfw/tests/BigBuffer_test.cpp new file mode 100644 index 000000000000..382d21e20846 --- /dev/null +++ b/libs/androidfw/tests/BigBuffer_test.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "androidfw/BigBuffer.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::NotNull; + +namespace android { + +TEST(BigBufferTest, AllocateSingleBlock) { + BigBuffer buffer(4); + + EXPECT_THAT(buffer.NextBlock<char>(2), NotNull()); + EXPECT_EQ(2u, buffer.size()); +} + +TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) { + BigBuffer buffer(16); + + char* b1 = buffer.NextBlock<char>(8); + EXPECT_THAT(b1, NotNull()); + + char* b2 = buffer.NextBlock<char>(4); + EXPECT_THAT(b2, NotNull()); + + EXPECT_EQ(b1 + 8, b2); +} + +TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) { + BigBuffer buffer(16); + + EXPECT_THAT(buffer.NextBlock<char>(32), NotNull()); + EXPECT_EQ(32u, buffer.size()); +} + +TEST(BigBufferTest, AppendAndMoveBlock) { + BigBuffer buffer(16); + + uint32_t* b1 = buffer.NextBlock<uint32_t>(); + ASSERT_THAT(b1, NotNull()); + *b1 = 33; + + { + BigBuffer buffer2(16); + b1 = buffer2.NextBlock<uint32_t>(); + ASSERT_THAT(b1, NotNull()); + *b1 = 44; + + buffer.AppendBuffer(std::move(buffer2)); + EXPECT_EQ(0u, buffer2.size()); // NOLINT + EXPECT_EQ(buffer2.begin(), buffer2.end()); + } + + EXPECT_EQ(2 * sizeof(uint32_t), buffer.size()); + + auto b = buffer.begin(); + ASSERT_NE(b, buffer.end()); + ASSERT_EQ(sizeof(uint32_t), b->size); + ASSERT_EQ(33u, *reinterpret_cast<uint32_t*>(b->buffer.get())); + ++b; + + ASSERT_NE(b, buffer.end()); + ASSERT_EQ(sizeof(uint32_t), b->size); + ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get())); + ++b; + + ASSERT_EQ(b, buffer.end()); +} + +TEST(BigBufferTest, PadAndAlignProperly) { + BigBuffer buffer(16); + + ASSERT_THAT(buffer.NextBlock<char>(2), NotNull()); + ASSERT_EQ(2u, buffer.size()); + buffer.Pad(2); + ASSERT_EQ(4u, buffer.size()); + buffer.Align4(); + ASSERT_EQ(4u, buffer.size()); + buffer.Pad(2); + ASSERT_EQ(6u, buffer.size()); + buffer.Align4(); + ASSERT_EQ(8u, buffer.size()); +} + +} // namespace android diff --git a/libs/androidfw/tests/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp index 5d464c7dc0f7..9c36cfb212c5 100644 --- a/libs/androidfw/tests/ByteBucketArray_test.cpp +++ b/libs/androidfw/tests/ByteBucketArray_test.cpp @@ -52,4 +52,57 @@ TEST(ByteBucketArrayTest, TestSparseInsertion) { } } +TEST(ByteBucketArrayTest, TestForEach) { + ByteBucketArray<int> bba; + ASSERT_TRUE(bba.set(0, 1)); + ASSERT_TRUE(bba.set(10, 2)); + ASSERT_TRUE(bba.set(26, 3)); + ASSERT_TRUE(bba.set(129, 4)); + ASSERT_TRUE(bba.set(234, 5)); + + int count = 0; + bba.forEachItem([&count](auto i, auto val) { + ++count; + switch (i) { + case 0: + EXPECT_EQ(1, val); + break; + case 10: + EXPECT_EQ(2, val); + break; + case 26: + EXPECT_EQ(3, val); + break; + case 129: + EXPECT_EQ(4, val); + break; + case 234: + EXPECT_EQ(5, val); + break; + default: + EXPECT_EQ(0, val); + break; + } + }); + ASSERT_EQ(4 * 16, count); +} + +TEST(ByteBucketArrayTest, TestTrimBuckets) { + ByteBucketArray<int> bba; + ASSERT_TRUE(bba.set(0, 1)); + ASSERT_TRUE(bba.set(255, 2)); + { + bba.trimBuckets([](auto val) { return val < 2; }); + int count = 0; + bba.forEachItem([&count](auto, auto) { ++count; }); + ASSERT_EQ(1 * 16, count); + } + { + bba.trimBuckets([](auto val) { return val < 3; }); + int count = 0; + bba.forEachItem([&count](auto, auto) { ++count; }); + ASSERT_EQ(0, count); + } +} + } // namespace android diff --git a/libs/androidfw/tests/ConfigDescription_test.cpp b/libs/androidfw/tests/ConfigDescription_test.cpp index ce7f8054e2ca..8fed0a4d22fc 100644 --- a/libs/androidfw/tests/ConfigDescription_test.cpp +++ b/libs/androidfw/tests/ConfigDescription_test.cpp @@ -25,8 +25,8 @@ namespace android { -static ::testing::AssertionResult TestParse( - const StringPiece& input, ConfigDescription* config = nullptr) { +static ::testing::AssertionResult TestParse(StringPiece input, + ConfigDescription* config = nullptr) { if (ConfigDescription::Parse(input, config)) { return ::testing::AssertionSuccess() << input << " was successfully parsed"; } @@ -138,7 +138,7 @@ TEST(ConfigDescriptionTest, ParseVrAttribute) { EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string()); } -static inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) { +static inline ConfigDescription ParseConfigOrDie(android::StringPiece str) { ConfigDescription config; CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str; return config; diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index d214e2dfef7b..c90ec197b5ef 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -71,62 +71,6 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) { ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value()); } -TEST(LoadedArscTest, LoadSparseEntryApp) { - std::string contents; - ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc", - &contents)); - - std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), - contents.length()); - ASSERT_THAT(loaded_arsc, NotNull()); - - const LoadedPackage* package = - loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9)); - ASSERT_THAT(package, NotNull()); - - const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1; - const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9); - - const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); - ASSERT_THAT(type_spec, NotNull()); - ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); - - auto type = type_spec->type_entries[0]; - ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value()); -} - -TEST(LoadedArscTest, FindSparseEntryApp) { - std::string contents; - ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc", - &contents)); - - std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), - contents.length()); - ASSERT_THAT(loaded_arsc, NotNull()); - - const LoadedPackage* package = - loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_v26)); - ASSERT_THAT(package, NotNull()); - - const uint8_t type_index = get_type_id(sparse::R::string::only_v26) - 1; - const uint16_t entry_index = get_entry_id(sparse::R::string::only_v26); - - const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); - ASSERT_THAT(type_spec, NotNull()); - ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); - - // Ensure that AAPT2 sparsely encoded the v26 config as expected. - auto type_entry = std::find_if( - type_spec->type_entries.begin(), type_spec->type_entries.end(), - [](const TypeSpec::TypeEntry& x) { return x.config.sdkVersion == 26; }); - ASSERT_NE(type_entry, type_spec->type_entries.end()); - ASSERT_NE(type_entry->type->flags & ResTable_type::FLAG_SPARSE, 0); - - // Test fetching a resource with only sparsely encoded configs by name. - auto id = package->FindEntryByName(u"string", u"only_v26"); - ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_v26, 0)); -} - TEST(LoadedArscTest, LoadSharedLibrary) { std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc", @@ -404,4 +348,84 @@ TEST(LoadedArscTest, LoadCustomLoader) { // sizeof(Res_value) might not be backwards compatible. // TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); } +class LoadedArscParameterizedTest : + public testing::TestWithParam<std::string> { +}; + +TEST_P(LoadedArscParameterizedTest, LoadSparseEntryApp) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents)); + + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), + contents.length()); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9)); + ASSERT_THAT(package, NotNull()); + + const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1; + const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9); + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); + + auto type = type_spec->type_entries[0]; + ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value()); +} + +TEST_P(LoadedArscParameterizedTest, FindSparseEntryApp) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents)); + + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), + contents.length()); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_land)); + ASSERT_THAT(package, NotNull()); + + const uint8_t type_index = get_type_id(sparse::R::string::only_land) - 1; + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); + + // Type Entry with default orientation is not sparse encoded because the ratio of + // populated entries to total entries is above threshold. + // Only find out default locale because Soong build system will introduce pseudo + // locales for the apk generated at runtime. + auto type_entry_default = std::find_if( + type_spec->type_entries.begin(), type_spec->type_entries.end(), + [] (const TypeSpec::TypeEntry& x) { return x.config.orientation == 0 && + x.config.locale == 0; }); + ASSERT_NE(type_entry_default, type_spec->type_entries.end()); + ASSERT_EQ(type_entry_default->type->flags & ResTable_type::FLAG_SPARSE, 0); + + // Type Entry with land orientation is sparse encoded as expected. + // Only find out default locale because Soong build system will introduce pseudo + // locales for the apk generated at runtime. + auto type_entry_land = std::find_if( + type_spec->type_entries.begin(), type_spec->type_entries.end(), + [](const TypeSpec::TypeEntry& x) { return x.config.orientation == + ResTable_config::ORIENTATION_LAND && + x.config.locale == 0; }); + ASSERT_NE(type_entry_land, type_spec->type_entries.end()); + ASSERT_NE(type_entry_land->type->flags & ResTable_type::FLAG_SPARSE, 0); + + // Test fetching a resource with only sparsely encoded configs by name. + auto id = package->FindEntryByName(u"string", u"only_land"); + ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_land, 0)); +} + +INSTANTIATE_TEST_SUITE_P( + FrameWorkResourcesLoadedArscTests, + LoadedArscParameterizedTest, + ::testing::Values( + base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk", + base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk" + )); + } // namespace android diff --git a/libs/androidfw/tests/PosixUtils_test.cpp b/libs/androidfw/tests/PosixUtils_test.cpp index 8c49350796ec..097e6b0bba65 100644 --- a/libs/androidfw/tests/PosixUtils_test.cpp +++ b/libs/androidfw/tests/PosixUtils_test.cpp @@ -28,27 +28,27 @@ namespace util { TEST(PosixUtilsTest, AbsolutePathToBinary) { const auto result = ExecuteBinary({"/bin/date", "--help"}); - ASSERT_THAT(result, NotNull()); - ASSERT_EQ(result->status, 0); - ASSERT_GE(result->stdout_str.find("usage: date "), 0); + ASSERT_TRUE((bool)result); + ASSERT_EQ(result.status, 0); + ASSERT_GE(result.stdout_str.find("usage: date "), 0); } TEST(PosixUtilsTest, RelativePathToBinary) { const auto result = ExecuteBinary({"date", "--help"}); - ASSERT_THAT(result, NotNull()); - ASSERT_EQ(result->status, 0); - ASSERT_GE(result->stdout_str.find("usage: date "), 0); + ASSERT_TRUE((bool)result); + ASSERT_EQ(result.status, 0); + ASSERT_GE(result.stdout_str.find("usage: date "), 0); } TEST(PosixUtilsTest, BadParameters) { const auto result = ExecuteBinary({"/bin/date", "--this-parameter-is-not-supported"}); - ASSERT_THAT(result, NotNull()); - ASSERT_NE(result->status, 0); + ASSERT_TRUE((bool)result); + ASSERT_GT(result.status, 0); } TEST(PosixUtilsTest, NoSuchBinary) { const auto result = ExecuteBinary({"/this/binary/does/not/exist"}); - ASSERT_THAT(result, IsNull()); + ASSERT_FALSE((bool)result); } } // android diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp index 9aeb00c47e63..fbf70981f2de 100644 --- a/libs/androidfw/tests/ResTable_test.cpp +++ b/libs/androidfw/tests/ResTable_test.cpp @@ -15,6 +15,7 @@ */ #include "androidfw/ResourceTypes.h" +#include "android-base/file.h" #include <codecvt> #include <locale> @@ -41,34 +42,6 @@ TEST(ResTableTest, ShouldLoadSuccessfully) { ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size())); } -TEST(ResTableTest, ShouldLoadSparseEntriesSuccessfully) { - std::string contents; - ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc", - &contents)); - - ResTable table; - ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size())); - - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 26; - table.setParameters(&config); - - String16 name(u"com.android.sparse:integer/foo_9"); - uint32_t flags; - uint32_t resid = - table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags); - ASSERT_NE(0u, resid); - - Res_value val; - ResTable_config selected_config; - ASSERT_GE( - table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config), - 0); - EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType); - EXPECT_EQ(900u, val.data); -} - TEST(ResTableTest, SimpleTypeIsRetrievedCorrectly) { std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", @@ -476,4 +449,43 @@ TEST(ResTableTest, TruncatedEncodeLength) { ASSERT_FALSE(invalid_pool->stringAt(invalid_val.data).has_value()); } +class ResTableParameterizedTest : + public testing::TestWithParam<std::string> { +}; + +TEST_P(ResTableParameterizedTest, ShouldLoadSparseEntriesSuccessfully) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents)); + + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size())); + + ResTable_config config; + memset(&config, 0, sizeof(config)); + config.orientation = ResTable_config::ORIENTATION_LAND; + table.setParameters(&config); + + String16 name(u"com.android.sparse:integer/foo_9"); + uint32_t flags; + uint32_t resid = + table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags); + ASSERT_NE(0u, resid); + + Res_value val; + ResTable_config selected_config; + ASSERT_GE( + table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config), + 0); + EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType); + EXPECT_EQ(900u, val.data); +} + +INSTANTIATE_TEST_SUITE_P( + FrameWorkResourcesResTableTests, + ResTableParameterizedTest, + ::testing::Values( + base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk", + base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk" + )); + } // namespace android diff --git a/libs/androidfw/tests/ResourceTimer_test.cpp b/libs/androidfw/tests/ResourceTimer_test.cpp new file mode 100644 index 000000000000..4a1e9735de7a --- /dev/null +++ b/libs/androidfw/tests/ResourceTimer_test.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2022 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. + */ + + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <androidfw/Util.h> + +#include "TestHelpers.h" + +#include <androidfw/ResourceTimer.h> + +namespace android { + +namespace { + +// Create a reading in us. This is a convenience function to avoid multiplying by 1000 +// everywhere. +unsigned int US(int us) { + return us * 1000; +} + +} + +TEST(ResourceTimerTest, TimerBasic) { + ResourceTimer::Timer timer; + ASSERT_THAT(timer.count, 0); + ASSERT_THAT(timer.total, 0); + + for (int i = 1; i <= 100; i++) { + timer.record(US(i)); + } + ASSERT_THAT(timer.count, 100); + ASSERT_THAT(timer.total, US((101 * 100)/2)); + ASSERT_THAT(timer.mintime, US(1)); + ASSERT_THAT(timer.maxtime, US(100)); + ASSERT_THAT(timer.pvalues.p50.floor, 0); + ASSERT_THAT(timer.pvalues.p50.nominal, 0); + ASSERT_THAT(timer.largest[0], US(100)); + ASSERT_THAT(timer.largest[1], US(99)); + ASSERT_THAT(timer.largest[2], US(98)); + ASSERT_THAT(timer.largest[3], US(97)); + ASSERT_THAT(timer.largest[4], US(96)); + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(49)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(50)); + ASSERT_THAT(timer.pvalues.p90.floor, US(89)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(90)); + ASSERT_THAT(timer.pvalues.p95.floor, US(94)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(95)); + ASSERT_THAT(timer.pvalues.p99.floor, US(98)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(99)); + + // Test reset functionality. All values should be zero after the reset. Computing pvalues + // after the result should also yield zeros. + timer.reset(); + ASSERT_THAT(timer.count, 0); + ASSERT_THAT(timer.total, 0); + ASSERT_THAT(timer.mintime, US(0)); + ASSERT_THAT(timer.maxtime, US(0)); + ASSERT_THAT(timer.pvalues.p50.floor, US(0)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(0)); + ASSERT_THAT(timer.largest[0], US(0)); + ASSERT_THAT(timer.largest[1], US(0)); + ASSERT_THAT(timer.largest[2], US(0)); + ASSERT_THAT(timer.largest[3], US(0)); + ASSERT_THAT(timer.largest[4], US(0)); + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(0)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(0)); + ASSERT_THAT(timer.pvalues.p90.floor, US(0)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(0)); + ASSERT_THAT(timer.pvalues.p95.floor, US(0)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(0)); + ASSERT_THAT(timer.pvalues.p99.floor, US(0)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(0)); + + // Test again, adding elements in reverse. + for (int i = 100; i >= 1; i--) { + timer.record(US(i)); + } + ASSERT_THAT(timer.count, 100); + ASSERT_THAT(timer.total, US((101 * 100)/2)); + ASSERT_THAT(timer.mintime, US(1)); + ASSERT_THAT(timer.maxtime, US(100)); + ASSERT_THAT(timer.pvalues.p50.floor, 0); + ASSERT_THAT(timer.pvalues.p50.nominal, 0); + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(49)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(50)); + ASSERT_THAT(timer.pvalues.p90.floor, US(89)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(90)); + ASSERT_THAT(timer.pvalues.p95.floor, US(94)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(95)); + ASSERT_THAT(timer.pvalues.p99.floor, US(98)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(99)); + ASSERT_THAT(timer.largest[0], US(100)); + ASSERT_THAT(timer.largest[1], US(99)); + ASSERT_THAT(timer.largest[2], US(98)); + ASSERT_THAT(timer.largest[3], US(97)); + ASSERT_THAT(timer.largest[4], US(96)); +} + +TEST(ResourceTimerTest, TimerLimit) { + ResourceTimer::Timer timer; + + // Event truncation means that a time of 1050us will be stored in the 1000us + // bucket. Since there is a single event, all p-values lie in the same range. + timer.record(US(1050)); + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(900)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(1000)); + ASSERT_THAT(timer.pvalues.p90.floor, US(900)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(1000)); + ASSERT_THAT(timer.pvalues.p95.floor, US(900)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(1000)); + ASSERT_THAT(timer.pvalues.p99.floor, US(900)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(1000)); +} + +TEST(ResourceTimerTest, TimerCopy) { + ResourceTimer::Timer source; + for (int i = 1; i <= 100; i++) { + source.record(US(i)); + } + ResourceTimer::Timer timer; + ResourceTimer::Timer::copy(timer, source, true); + ASSERT_THAT(source.count, 0); + ASSERT_THAT(source.total, 0); + // compute() is not normally be called on a reset timer, but it should work and it should return + // all zeros. + source.compute(); + ASSERT_THAT(source.pvalues.p50.floor, US(0)); + ASSERT_THAT(source.pvalues.p50.nominal, US(0)); + ASSERT_THAT(source.pvalues.p90.floor, US(0)); + ASSERT_THAT(source.pvalues.p90.nominal, US(0)); + ASSERT_THAT(source.pvalues.p95.floor, US(0)); + ASSERT_THAT(source.pvalues.p95.nominal, US(0)); + ASSERT_THAT(source.pvalues.p99.floor, US(0)); + ASSERT_THAT(source.pvalues.p99.nominal, US(0)); + ASSERT_THAT(source.largest[0], US(0)); + ASSERT_THAT(source.largest[1], US(0)); + ASSERT_THAT(source.largest[2], US(0)); + ASSERT_THAT(source.largest[3], US(0)); + ASSERT_THAT(source.largest[4], US(0)); + + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(49)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(50)); + ASSERT_THAT(timer.pvalues.p90.floor, US(89)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(90)); + ASSERT_THAT(timer.pvalues.p95.floor, US(94)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(95)); + ASSERT_THAT(timer.pvalues.p99.floor, US(98)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(99)); + ASSERT_THAT(timer.largest[0], US(100)); + ASSERT_THAT(timer.largest[1], US(99)); + ASSERT_THAT(timer.largest[2], US(98)); + ASSERT_THAT(timer.largest[3], US(97)); + ASSERT_THAT(timer.largest[4], US(96)); + + // Call compute a second time. The values must be the same. + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(49)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(50)); + ASSERT_THAT(timer.pvalues.p90.floor, US(89)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(90)); + ASSERT_THAT(timer.pvalues.p95.floor, US(94)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(95)); + ASSERT_THAT(timer.pvalues.p99.floor, US(98)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(99)); + ASSERT_THAT(timer.largest[0], US(100)); + ASSERT_THAT(timer.largest[1], US(99)); + ASSERT_THAT(timer.largest[2], US(98)); + ASSERT_THAT(timer.largest[3], US(97)); + ASSERT_THAT(timer.largest[4], US(96)); + + // Modify the source. If timer and source share histogram arrays, this will introduce an + // error. + for (int i = 1; i <= 100; i++) { + source.record(US(i)); + } + // Call compute a third time. The values must be the same. + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(49)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(50)); + ASSERT_THAT(timer.pvalues.p90.floor, US(89)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(90)); + ASSERT_THAT(timer.pvalues.p95.floor, US(94)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(95)); + ASSERT_THAT(timer.pvalues.p99.floor, US(98)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(99)); + ASSERT_THAT(timer.largest[0], US(100)); + ASSERT_THAT(timer.largest[1], US(99)); + ASSERT_THAT(timer.largest[2], US(98)); + ASSERT_THAT(timer.largest[3], US(97)); + ASSERT_THAT(timer.largest[4], US(96)); +} + +// Verify that if too many oversize entries are reported, the percentile values cannot be computed +// and are set to zero. +TEST(ResourceTimerTest, TimerOversize) { + static const int oversize = US(2 * 1000 * 1000); + + ResourceTimer::Timer timer; + for (int i = 1; i <= 100; i++) { + timer.record(US(i)); + } + + // Insert enough oversize values to invalidate the p90, p95, and p99 percentiles. The p50 is + // still computable. + for (int i = 1; i <= 50; i++) { + timer.record(oversize); + } + ASSERT_THAT(timer.largest[0], oversize); + ASSERT_THAT(timer.largest[1], oversize); + ASSERT_THAT(timer.largest[2], oversize); + ASSERT_THAT(timer.largest[3], oversize); + ASSERT_THAT(timer.largest[4], oversize); + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(74)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(75)); + ASSERT_THAT(timer.pvalues.p90.floor, 0); + ASSERT_THAT(timer.pvalues.p90.nominal, 0); + ASSERT_THAT(timer.pvalues.p95.floor, 0); + ASSERT_THAT(timer.pvalues.p95.nominal, 0); + ASSERT_THAT(timer.pvalues.p99.floor, 0); + ASSERT_THAT(timer.pvalues.p99.nominal, 0); +} + + +} // namespace android diff --git a/libs/androidfw/tests/SparseEntry_bench.cpp b/libs/androidfw/tests/SparseEntry_bench.cpp index c9b4ad8af278..fffeeb802873 100644 --- a/libs/androidfw/tests/SparseEntry_bench.cpp +++ b/libs/androidfw/tests/SparseEntry_bench.cpp @@ -16,6 +16,7 @@ #include "androidfw/AssetManager.h" #include "androidfw/ResourceTypes.h" +#include "android-base/file.h" #include "BenchmarkHelpers.h" #include "data/sparse/R.h" @@ -24,40 +25,74 @@ namespace sparse = com::android::sparse; namespace android { +static void BM_SparseEntryGetResourceHelper(const std::vector<std::string>& paths, + uint32_t resid, benchmark::State& state, void (*GetResourceBenchmarkFunc)( + const std::vector<std::string>&, const ResTable_config*, + uint32_t, benchmark::State&)){ + ResTable_config config; + memset(&config, 0, sizeof(config)); + config.orientation = ResTable_config::ORIENTATION_LAND; + GetResourceBenchmarkFunc(paths, &config, resid, state); +} + static void BM_SparseEntryGetResourceOldSparse(benchmark::State& state, uint32_t resid) { - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 26; - GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state); + BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid, + state, &GetResourceBenchmarkOld); } BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Small, sparse::R::integer::foo_9); BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Large, sparse::R::string::foo_999); static void BM_SparseEntryGetResourceOldNotSparse(benchmark::State& state, uint32_t resid) { - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 26; - GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state); + BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid, + state, &GetResourceBenchmarkOld); } BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Small, sparse::R::integer::foo_9); BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Large, sparse::R::string::foo_999); static void BM_SparseEntryGetResourceSparse(benchmark::State& state, uint32_t resid) { - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 26; - GetResourceBenchmark({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state); + BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid, + state, &GetResourceBenchmark); } BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Small, sparse::R::integer::foo_9); BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Large, sparse::R::string::foo_999); static void BM_SparseEntryGetResourceNotSparse(benchmark::State& state, uint32_t resid) { - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 26; - GetResourceBenchmark({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state); + BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid, + state, &GetResourceBenchmark); } BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Small, sparse::R::integer::foo_9); BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Large, sparse::R::string::foo_999); +static void BM_SparseEntryGetResourceOldSparseRuntime(benchmark::State& state, uint32_t resid) { + BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() + + "/FrameworkResourcesSparseTestApp.apk"}, resid, state, + &GetResourceBenchmarkOld); +} +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Small, sparse::R::integer::foo_9); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Large, sparse::R::string::foo_999); + +static void BM_SparseEntryGetResourceOldNotSparseRuntime(benchmark::State& state, uint32_t resid) { + BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() + + "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state, + &GetResourceBenchmarkOld); +} +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Small, sparse::R::integer::foo_9); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Large, sparse::R::string::foo_999); + +static void BM_SparseEntryGetResourceSparseRuntime(benchmark::State& state, uint32_t resid) { + BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() + + "/FrameworkResourcesSparseTestApp.apk"}, resid, state, + &GetResourceBenchmark); +} +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Small, sparse::R::integer::foo_9); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Large, sparse::R::string::foo_999); + +static void BM_SparseEntryGetResourceNotSparseRuntime(benchmark::State& state, uint32_t resid) { + BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() + + "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state, + &GetResourceBenchmark); +} +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Small, sparse::R::integer::foo_9); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Large, sparse::R::string::foo_999); + } // namespace android diff --git a/libs/androidfw/tests/StringPiece_test.cpp b/libs/androidfw/tests/StringPiece_test.cpp index 316a5c1bf40e..822e527253df 100644 --- a/libs/androidfw/tests/StringPiece_test.cpp +++ b/libs/androidfw/tests/StringPiece_test.cpp @@ -60,36 +60,4 @@ TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) { EXPECT_TRUE(StringPiece(car) > banana); } -TEST(StringPieceTest, ContainsOtherStringPiece) { - StringPiece text("I am a leaf on the wind."); - StringPiece start_needle("I am"); - StringPiece end_needle("wind."); - StringPiece middle_needle("leaf"); - StringPiece empty_needle(""); - StringPiece missing_needle("soar"); - StringPiece long_needle("This string is longer than the text."); - - EXPECT_TRUE(text.contains(start_needle)); - EXPECT_TRUE(text.contains(end_needle)); - EXPECT_TRUE(text.contains(middle_needle)); - EXPECT_TRUE(text.contains(empty_needle)); - EXPECT_FALSE(text.contains(missing_needle)); - EXPECT_FALSE(text.contains(long_needle)); - - StringPiece16 text16(u"I am a leaf on the wind."); - StringPiece16 start_needle16(u"I am"); - StringPiece16 end_needle16(u"wind."); - StringPiece16 middle_needle16(u"leaf"); - StringPiece16 empty_needle16(u""); - StringPiece16 missing_needle16(u"soar"); - StringPiece16 long_needle16(u"This string is longer than the text."); - - EXPECT_TRUE(text16.contains(start_needle16)); - EXPECT_TRUE(text16.contains(end_needle16)); - EXPECT_TRUE(text16.contains(middle_needle16)); - EXPECT_TRUE(text16.contains(empty_needle16)); - EXPECT_FALSE(text16.contains(missing_needle16)); - EXPECT_FALSE(text16.contains(long_needle16)); -} - } // namespace android diff --git a/libs/androidfw/tests/StringPool_test.cpp b/libs/androidfw/tests/StringPool_test.cpp new file mode 100644 index 000000000000..0e0acae165d9 --- /dev/null +++ b/libs/androidfw/tests/StringPool_test.cpp @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "androidfw/StringPool.h" + +#include <string> + +#include "androidfw/IDiagnostics.h" +#include "androidfw/StringPiece.h" +#include "androidfw/Util.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::android::StringPiece; +using ::android::StringPiece16; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::NotNull; +using ::testing::Pointee; + +namespace android { + +TEST(StringPoolTest, InsertOneString) { + StringPool pool; + + StringPool::Ref ref = pool.MakeRef("wut"); + EXPECT_THAT(*ref, Eq("wut")); +} + +TEST(StringPoolTest, InsertTwoUniqueStrings) { + StringPool pool; + + StringPool::Ref ref_a = pool.MakeRef("wut"); + StringPool::Ref ref_b = pool.MakeRef("hey"); + + EXPECT_THAT(*ref_a, Eq("wut")); + EXPECT_THAT(*ref_b, Eq("hey")); +} + +TEST(StringPoolTest, DoNotInsertNewDuplicateString) { + StringPool pool; + + StringPool::Ref ref_a = pool.MakeRef("wut"); + StringPool::Ref ref_b = pool.MakeRef("wut"); + + EXPECT_THAT(*ref_a, Eq("wut")); + EXPECT_THAT(*ref_b, Eq("wut")); + EXPECT_THAT(pool.size(), Eq(1u)); +} + +TEST(StringPoolTest, DoNotDedupeSameStringDifferentPriority) { + StringPool pool; + + StringPool::Ref ref_a = pool.MakeRef("wut", StringPool::Context(0x81010001)); + StringPool::Ref ref_b = pool.MakeRef("wut", StringPool::Context(0x81010002)); + + EXPECT_THAT(*ref_a, Eq("wut")); + EXPECT_THAT(*ref_b, Eq("wut")); + EXPECT_THAT(pool.size(), Eq(2u)); +} + +TEST(StringPoolTest, MaintainInsertionOrderIndex) { + StringPool pool; + + StringPool::Ref ref_a = pool.MakeRef("z"); + StringPool::Ref ref_b = pool.MakeRef("a"); + StringPool::Ref ref_c = pool.MakeRef("m"); + + EXPECT_THAT(ref_a.index(), Eq(0u)); + EXPECT_THAT(ref_b.index(), Eq(1u)); + EXPECT_THAT(ref_c.index(), Eq(2u)); +} + +TEST(StringPoolTest, PruneStringsWithNoReferences) { + StringPool pool; + + StringPool::Ref ref_a = pool.MakeRef("foo"); + + { + StringPool::Ref ref_b = pool.MakeRef("wut"); + EXPECT_THAT(*ref_b, Eq("wut")); + EXPECT_THAT(pool.size(), Eq(2u)); + pool.Prune(); + EXPECT_THAT(pool.size(), Eq(2u)); + } + EXPECT_THAT(pool.size(), Eq(2u)); + + { + StringPool::Ref ref_c = pool.MakeRef("bar"); + EXPECT_THAT(pool.size(), Eq(3u)); + + pool.Prune(); + EXPECT_THAT(pool.size(), Eq(2u)); + } + EXPECT_THAT(pool.size(), Eq(2u)); + + pool.Prune(); + EXPECT_THAT(pool.size(), Eq(1u)); +} + +TEST(StringPoolTest, SortAndMaintainIndexesInStringReferences) { + StringPool pool; + + StringPool::Ref ref_a = pool.MakeRef("z"); + StringPool::Ref ref_b = pool.MakeRef("a"); + StringPool::Ref ref_c = pool.MakeRef("m"); + + EXPECT_THAT(*ref_a, Eq("z")); + EXPECT_THAT(ref_a.index(), Eq(0u)); + + EXPECT_THAT(*ref_b, Eq("a")); + EXPECT_THAT(ref_b.index(), Eq(1u)); + + EXPECT_THAT(*ref_c, Eq("m")); + EXPECT_THAT(ref_c.index(), Eq(2u)); + + pool.Sort(); + + EXPECT_THAT(*ref_a, Eq("z")); + EXPECT_THAT(ref_a.index(), Eq(2u)); + + EXPECT_THAT(*ref_b, Eq("a")); + EXPECT_THAT(ref_b.index(), Eq(0u)); + + EXPECT_THAT(*ref_c, Eq("m")); + EXPECT_THAT(ref_c.index(), Eq(1u)); +} + +TEST(StringPoolTest, SortAndStillDedupe) { + StringPool pool; + + StringPool::Ref ref_a = pool.MakeRef("z"); + StringPool::Ref ref_b = pool.MakeRef("a"); + StringPool::Ref ref_c = pool.MakeRef("m"); + + pool.Sort(); + + StringPool::Ref ref_d = pool.MakeRef("z"); + StringPool::Ref ref_e = pool.MakeRef("a"); + StringPool::Ref ref_f = pool.MakeRef("m"); + + EXPECT_THAT(ref_d.index(), Eq(ref_a.index())); + EXPECT_THAT(ref_e.index(), Eq(ref_b.index())); + EXPECT_THAT(ref_f.index(), Eq(ref_c.index())); +} + +TEST(StringPoolTest, AddStyles) { + StringPool pool; + + StringPool::StyleRef ref = pool.MakeRef(StyleString{{"android"}, {Span{{"b"}, 2, 6}}}); + EXPECT_THAT(ref.index(), Eq(0u)); + EXPECT_THAT(ref->value, Eq("android")); + ASSERT_THAT(ref->spans.size(), Eq(1u)); + + const StringPool::Span& span = ref->spans.front(); + EXPECT_THAT(*span.name, Eq("b")); + EXPECT_THAT(span.first_char, Eq(2u)); + EXPECT_THAT(span.last_char, Eq(6u)); +} + +TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { + StringPool pool; + + StringPool::Ref ref = pool.MakeRef("android"); + + StyleString str{{"android"}, {}}; + StringPool::StyleRef style_ref = pool.MakeRef(StyleString{{"android"}, {}}); + + EXPECT_THAT(ref.index(), Ne(style_ref.index())); +} + +TEST(StringPoolTest, StylesAndStringsAreSeparateAfterSorting) { + StringPool pool; + + StringPool::StyleRef ref_a = pool.MakeRef(StyleString{{"beta"}, {}}); + StringPool::Ref ref_b = pool.MakeRef("alpha"); + StringPool::StyleRef ref_c = pool.MakeRef(StyleString{{"alpha"}, {}}); + + EXPECT_THAT(ref_b.index(), Ne(ref_c.index())); + + pool.Sort(); + + EXPECT_THAT(ref_c.index(), Eq(0u)); + EXPECT_THAT(ref_a.index(), Eq(1u)); + EXPECT_THAT(ref_b.index(), Eq(2u)); +} + +TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { + using namespace android; // For NO_ERROR on Windows. + NoOpDiagnostics diag; + + StringPool pool; + BigBuffer buffer(1024); + StringPool::FlattenUtf8(&buffer, pool, &diag); + + std::unique_ptr<uint8_t[]> data = android::util::Copy(buffer); + ResStringPool test; + ASSERT_THAT(test.setTo(data.get(), buffer.size()), Eq(NO_ERROR)); +} + +TEST(StringPoolTest, FlattenOddCharactersUtf16) { + using namespace android; // For NO_ERROR on Windows. + NoOpDiagnostics diag; + + StringPool pool; + pool.MakeRef("\u093f"); + BigBuffer buffer(1024); + StringPool::FlattenUtf16(&buffer, pool, &diag); + + std::unique_ptr<uint8_t[]> data = android::util::Copy(buffer); + ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); + auto str = test.stringAt(0); + ASSERT_TRUE(str.has_value()); + EXPECT_THAT(str->size(), Eq(1u)); + EXPECT_THAT(str->data(), Pointee(Eq(u'\u093f'))); + EXPECT_THAT(str->data()[1], Eq(0u)); +} + +constexpr const char* sLongString = + "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑" + "え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限" + "します。メール、SMSや、同期を使 " + "用するその他のアプリは、起動しても更新されないことがあります。バッテリーセ" + "ーバーは端末の充電中は自動的にOFFになります。"; + +TEST(StringPoolTest, Flatten) { + using namespace android; // For NO_ERROR on Windows. + NoOpDiagnostics diag; + + StringPool pool; + + StringPool::Ref ref_a = pool.MakeRef("hello"); + StringPool::Ref ref_b = pool.MakeRef("goodbye"); + StringPool::Ref ref_c = pool.MakeRef(sLongString); + StringPool::Ref ref_d = pool.MakeRef(""); + StringPool::StyleRef ref_e = + pool.MakeRef(StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}}); + + // Styles are always first. + EXPECT_THAT(ref_e.index(), Eq(0u)); + + EXPECT_THAT(ref_a.index(), Eq(1u)); + EXPECT_THAT(ref_b.index(), Eq(2u)); + EXPECT_THAT(ref_c.index(), Eq(3u)); + EXPECT_THAT(ref_d.index(), Eq(4u)); + + BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; + StringPool::FlattenUtf8(&buffers[0], pool, &diag); + StringPool::FlattenUtf16(&buffers[1], pool, &diag); + + // Test both UTF-8 and UTF-16 buffers. + for (const BigBuffer& buffer : buffers) { + std::unique_ptr<uint8_t[]> data = android::util::Copy(buffer); + + ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); + + EXPECT_THAT(android::util::GetString(test, 1), Eq("hello")); + EXPECT_THAT(android::util::GetString16(test, 1), Eq(u"hello")); + + EXPECT_THAT(android::util::GetString(test, 2), Eq("goodbye")); + EXPECT_THAT(android::util::GetString16(test, 2), Eq(u"goodbye")); + + EXPECT_THAT(android::util::GetString(test, 3), Eq(sLongString)); + EXPECT_THAT(android::util::GetString16(test, 3), Eq(util::Utf8ToUtf16(sLongString))); + + EXPECT_TRUE(test.stringAt(4).has_value() || test.string8At(4).has_value()); + + EXPECT_THAT(android::util::GetString(test, 0), Eq("style")); + EXPECT_THAT(android::util::GetString16(test, 0), Eq(u"style")); + + auto span_result = test.styleAt(0); + ASSERT_TRUE(span_result.has_value()); + + const ResStringPool_span* span = span_result->unsafe_ptr(); + EXPECT_THAT(android::util::GetString(test, span->name.index), Eq("b")); + EXPECT_THAT(android::util::GetString16(test, span->name.index), Eq(u"b")); + EXPECT_THAT(span->firstChar, Eq(0u)); + EXPECT_THAT(span->lastChar, Eq(1u)); + span++; + + ASSERT_THAT(span->name.index, Ne(ResStringPool_span::END)); + EXPECT_THAT(android::util::GetString(test, span->name.index), Eq("i")); + EXPECT_THAT(android::util::GetString16(test, span->name.index), Eq(u"i")); + EXPECT_THAT(span->firstChar, Eq(2u)); + EXPECT_THAT(span->lastChar, Eq(3u)); + span++; + + EXPECT_THAT(span->name.index, Eq(ResStringPool_span::END)); + } +} + +TEST(StringPoolTest, ModifiedUTF8) { + using namespace android; // For NO_ERROR on Windows. + NoOpDiagnostics diag; + StringPool pool; + StringPool::Ref ref_a = pool.MakeRef("\xF0\x90\x90\x80"); // 𐐀 (U+10400) + StringPool::Ref ref_b = pool.MakeRef("foo \xF0\x90\x90\xB7 bar"); // 𐐷 (U+10437) + StringPool::Ref ref_c = pool.MakeRef("\xF0\x90\x90\x80\xF0\x90\x90\xB7"); + + BigBuffer buffer(1024); + StringPool::FlattenUtf8(&buffer, pool, &diag); + std::unique_ptr<uint8_t[]> data = android::util::Copy(buffer); + + // Check that the codepoints are encoded using two three-byte surrogate pairs + ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); + auto str = test.string8At(0); + ASSERT_TRUE(str.has_value()); + EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80")); + + str = test.string8At(1); + ASSERT_TRUE(str.has_value()); + EXPECT_THAT(*str, Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar")); + + str = test.string8At(2); + ASSERT_TRUE(str.has_value()); + EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7")); + + // Check that retrieving the strings returns the original UTF-8 character bytes + EXPECT_THAT(android::util::GetString(test, 0), Eq("\xF0\x90\x90\x80")); + EXPECT_THAT(android::util::GetString(test, 1), Eq("foo \xF0\x90\x90\xB7 bar")); + EXPECT_THAT(android::util::GetString(test, 2), Eq("\xF0\x90\x90\x80\xF0\x90\x90\xB7")); +} + +TEST(StringPoolTest, MaxEncodingLength) { + NoOpDiagnostics diag; + using namespace android; // For NO_ERROR on Windows. + ResStringPool test; + + StringPool pool; + pool.MakeRef("aaaaaaaaaa"); + BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; + + // Make sure a UTF-8 string under the maximum length does not produce an error + EXPECT_THAT(StringPool::FlattenUtf8(&buffers[0], pool, &diag), Eq(true)); + std::unique_ptr<uint8_t[]> data = android::util::Copy(buffers[0]); + test.setTo(data.get(), buffers[0].size()); + EXPECT_THAT(android::util::GetString(test, 0), Eq("aaaaaaaaaa")); + + // Make sure a UTF-16 string under the maximum length does not produce an error + EXPECT_THAT(StringPool::FlattenUtf16(&buffers[1], pool, &diag), Eq(true)); + data = android::util::Copy(buffers[1]); + test.setTo(data.get(), buffers[1].size()); + EXPECT_THAT(android::util::GetString16(test, 0), Eq(u"aaaaaaaaaa")); + + StringPool pool2; + std::string longStr(50000, 'a'); + pool2.MakeRef("this fits1"); + pool2.MakeRef(longStr); + pool2.MakeRef("this fits2"); + BigBuffer buffers2[2] = {BigBuffer(1024), BigBuffer(1024)}; + + // Make sure a string that exceeds the maximum length of UTF-8 produces an + // error and writes a shorter error string instead + EXPECT_THAT(StringPool::FlattenUtf8(&buffers2[0], pool2, &diag), Eq(false)); + data = android::util::Copy(buffers2[0]); + test.setTo(data.get(), buffers2[0].size()); + EXPECT_THAT(android::util::GetString(test, 0), "this fits1"); + EXPECT_THAT(android::util::GetString(test, 1), "STRING_TOO_LARGE"); + EXPECT_THAT(android::util::GetString(test, 2), "this fits2"); + + // Make sure a string that a string that exceeds the maximum length of UTF-8 + // but not UTF-16 does not error for UTF-16 + StringPool pool3; + std::u16string longStr16(50000, 'a'); + pool3.MakeRef(longStr); + EXPECT_THAT(StringPool::FlattenUtf16(&buffers2[1], pool3, &diag), Eq(true)); + data = android::util::Copy(buffers2[1]); + test.setTo(data.get(), buffers2[1].size()); + EXPECT_THAT(android::util::GetString16(test, 0), Eq(longStr16)); +} + +} // namespace android diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp index d69abe5d0f11..ed30904ec179 100644 --- a/libs/androidfw/tests/TypeWrappers_test.cpp +++ b/libs/androidfw/tests/TypeWrappers_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <algorithm> #include <androidfw/ResourceTypes.h> #include <androidfw/TypeWrappers.h> #include <utils/String8.h> @@ -22,88 +23,123 @@ namespace android { -void* createTypeData() { - ResTable_type t; - memset(&t, 0, sizeof(t)); +// create a ResTable_type in memory with a vector of Res_value* +static ResTable_type* createTypeTable(std::vector<Res_value*>& values, + bool compact_entry = false, + bool short_offsets = false) +{ + ResTable_type t{}; t.header.type = RES_TABLE_TYPE_TYPE; t.header.headerSize = sizeof(t); + t.header.size = sizeof(t); t.id = 1; - t.entryCount = 3; - - uint32_t offsets[3]; - t.entriesStart = t.header.headerSize + sizeof(offsets); - t.header.size = t.entriesStart; - - offsets[0] = 0; - ResTable_entry e1; - memset(&e1, 0, sizeof(e1)); - e1.size = sizeof(e1); - e1.key.index = 0; - t.header.size += sizeof(e1); - - Res_value v1; - memset(&v1, 0, sizeof(v1)); - t.header.size += sizeof(v1); - - offsets[1] = ResTable_type::NO_ENTRY; - - offsets[2] = sizeof(e1) + sizeof(v1); - ResTable_entry e2; - memset(&e2, 0, sizeof(e2)); - e2.size = sizeof(e2); - e2.key.index = 1; - t.header.size += sizeof(e2); - - Res_value v2; - memset(&v2, 0, sizeof(v2)); - t.header.size += sizeof(v2); - - uint8_t* data = (uint8_t*)malloc(t.header.size); - uint8_t* p = data; - memcpy(p, &t, sizeof(t)); - p += sizeof(t); - memcpy(p, offsets, sizeof(offsets)); - p += sizeof(offsets); - memcpy(p, &e1, sizeof(e1)); - p += sizeof(e1); - memcpy(p, &v1, sizeof(v1)); - p += sizeof(v1); - memcpy(p, &e2, sizeof(e2)); - p += sizeof(e2); - memcpy(p, &v2, sizeof(v2)); - p += sizeof(v2); - return data; + t.flags = short_offsets ? ResTable_type::FLAG_OFFSET16 : 0; + + t.header.size += values.size() * (short_offsets ? sizeof(uint16_t) : sizeof(uint32_t)); + t.entriesStart = t.header.size; + t.entryCount = values.size(); + + size_t entry_size = compact_entry ? sizeof(ResTable_entry) + : sizeof(ResTable_entry) + sizeof(Res_value); + for (auto const v : values) { + t.header.size += v ? entry_size : 0; + } + + uint8_t* data = (uint8_t *)malloc(t.header.size); + uint8_t* p_header = data; + uint8_t* p_offsets = data + t.header.headerSize; + uint8_t* p_entries = data + t.entriesStart; + + memcpy(p_header, &t, sizeof(t)); + + size_t i = 0, entry_offset = 0; + uint32_t k = 0; + for (auto const& v : values) { + if (short_offsets) { + uint16_t *p = reinterpret_cast<uint16_t *>(p_offsets) + i; + *p = v ? (entry_offset >> 2) & 0xffffu : 0xffffu; + } else { + uint32_t *p = reinterpret_cast<uint32_t *>(p_offsets) + i; + *p = v ? entry_offset : ResTable_type::NO_ENTRY; + } + + if (v) { + ResTable_entry entry{}; + if (compact_entry) { + entry.compact.key = i; + entry.compact.flags = ResTable_entry::FLAG_COMPACT | (v->dataType << 8); + entry.compact.data = v->data; + memcpy(p_entries, &entry, sizeof(entry)); p_entries += sizeof(entry); + entry_offset += sizeof(entry); + } else { + Res_value value{}; + entry.full.size = sizeof(entry); + entry.full.key.index = i; + value = *v; + memcpy(p_entries, &entry, sizeof(entry)); p_entries += sizeof(entry); + memcpy(p_entries, &value, sizeof(value)); p_entries += sizeof(value); + entry_offset += sizeof(entry) + sizeof(value); + } + } + i++; + } + return reinterpret_cast<ResTable_type*>(data); } TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) { - ResTable_type* data = (ResTable_type*) createTypeData(); + std::vector<Res_value *> values; - TypeVariant v(data); + Res_value *v1 = new Res_value{}; + values.push_back(v1); - TypeVariant::iterator iter = v.beginEntries(); - ASSERT_EQ(uint32_t(0), iter.index()); - ASSERT_TRUE(NULL != *iter); - ASSERT_EQ(uint32_t(0), iter->key.index); - ASSERT_NE(v.endEntries(), iter); + values.push_back(nullptr); - iter++; + Res_value *v2 = new Res_value{}; + values.push_back(v2); - ASSERT_EQ(uint32_t(1), iter.index()); - ASSERT_TRUE(NULL == *iter); - ASSERT_NE(v.endEntries(), iter); + Res_value *v3 = new Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678}; + values.push_back(v3); - iter++; + // test for combinations of compact_entry and short_offsets + for (size_t i = 0; i < 4; i++) { + bool compact_entry = i & 0x1, short_offsets = i & 0x2; + ResTable_type* data = createTypeTable(values, compact_entry, short_offsets); + TypeVariant v(data); - ASSERT_EQ(uint32_t(2), iter.index()); - ASSERT_TRUE(NULL != *iter); - ASSERT_EQ(uint32_t(1), iter->key.index); - ASSERT_NE(v.endEntries(), iter); + TypeVariant::iterator iter = v.beginEntries(); + ASSERT_EQ(uint32_t(0), iter.index()); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(uint32_t(0), iter->key()); + ASSERT_NE(v.endEntries(), iter); - iter++; + iter++; - ASSERT_EQ(v.endEntries(), iter); + ASSERT_EQ(uint32_t(1), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); - free(data); + iter++; + + ASSERT_EQ(uint32_t(2), iter.index()); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(uint32_t(2), iter->key()); + ASSERT_NE(v.endEntries(), iter); + + iter++; + + ASSERT_EQ(uint32_t(3), iter.index()); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(iter->is_compact(), compact_entry); + ASSERT_EQ(uint32_t(3), iter->key()); + ASSERT_EQ(uint32_t(0x12345678), iter->value().data); + ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType); + + iter++; + + ASSERT_EQ(v.endEntries(), iter); + + free(data); + } } } // namespace android diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap Binary files differindex 88eadccb38cf..8e847e81aa31 100644 --- a/libs/androidfw/tests/data/overlay/overlay.idmap +++ b/libs/androidfw/tests/data/overlay/overlay.idmap diff --git a/libs/androidfw/tests/data/sparse/Android.bp b/libs/androidfw/tests/data/sparse/Android.bp new file mode 100644 index 000000000000..b0da375c7971 --- /dev/null +++ b/libs/androidfw/tests/data/sparse/Android.bp @@ -0,0 +1,23 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_libs_androidfw_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_libs_androidfw_license"], +} + +android_test_helper_app { + name: "FrameworkResourcesSparseTestApp", + sdk_version: "current", + min_sdk_version: "32", + aaptflags: [ + "--enable-sparse-encoding", + ], +} + +android_test_helper_app { + name: "FrameworkResourcesNotSparseTestApp", + sdk_version: "current", + min_sdk_version: "32", +} diff --git a/libs/androidfw/tests/data/sparse/AndroidManifest.xml b/libs/androidfw/tests/data/sparse/AndroidManifest.xml index 27911b62447a..9c23a7227631 100644 --- a/libs/androidfw/tests/data/sparse/AndroidManifest.xml +++ b/libs/androidfw/tests/data/sparse/AndroidManifest.xml @@ -17,4 +17,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.sparse"> <application /> + <uses-sdk android:minSdkVersion="32" /> </manifest> diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h index 2492dbf33f4a..a66e1af150c4 100644 --- a/libs/androidfw/tests/data/sparse/R.h +++ b/libs/androidfw/tests/data/sparse/R.h @@ -42,7 +42,7 @@ struct R { struct string { enum : uint32_t { foo_999 = 0x7f0203e7, - only_v26 = 0x7f0203e8 + only_land = 0x7f0203e8 }; }; }; diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh index 4ea5468c7df9..114ecbb7d860 100755 --- a/libs/androidfw/tests/data/sparse/gen_strings.sh +++ b/libs/androidfw/tests/data/sparse/gen_strings.sh @@ -1,20 +1,20 @@ #!/bin/bash OUTPUT_default=res/values/strings.xml -OUTPUT_v26=res/values-v26/strings.xml +OUTPUT_land=res/values-land/strings.xml echo "<resources>" > $OUTPUT_default -echo "<resources>" > $OUTPUT_v26 +echo "<resources>" > $OUTPUT_land for i in {0..999} do echo " <string name=\"foo_$i\">$i</string>" >> $OUTPUT_default if [ "$(($i % 3))" -eq "0" ] then - echo " <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_v26 + echo " <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_land fi done echo "</resources>" >> $OUTPUT_default -echo " <string name=\"only_v26\">only v26</string>" >> $OUTPUT_v26 -echo "</resources>" >> $OUTPUT_v26 +echo " <string name=\"only_land\">only land</string>" >> $OUTPUT_land +echo "</resources>" >> $OUTPUT_land diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk Binary files differindex b08a621195c0..4d4d4a849033 100644 --- a/libs/androidfw/tests/data/sparse/not_sparse.apk +++ b/libs/androidfw/tests/data/sparse/not_sparse.apk diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml index d116087ec3c0..66222c327416 100644 --- a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml +++ b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml @@ -333,5 +333,5 @@ <string name="foo_993">9930</string> <string name="foo_996">9960</string> <string name="foo_999">9990</string> - <string name="only_v26">only v26</string> + <string name="only_land">only land</string> </resources> diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/values.xml b/libs/androidfw/tests/data/sparse/res/values-land/values.xml index b396ad24aa8c..b396ad24aa8c 100644 --- a/libs/androidfw/tests/data/sparse/res/values-v26/values.xml +++ b/libs/androidfw/tests/data/sparse/res/values-land/values.xml diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk Binary files differindex 9fd01fbf2941..0f2d75a62b96 100644 --- a/libs/androidfw/tests/data/sparse/sparse.apk +++ b/libs/androidfw/tests/data/sparse/sparse.apk diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 979a660d497f..aeead5efc48a 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -78,6 +78,7 @@ cc_defaults { "external/skia/src/utils", "external/skia/src/gpu", "external/skia/src/shaders", + "external/skia/modules/skottie", ], }, host: { @@ -338,6 +339,7 @@ cc_defaults { "jni/android_util_PathParser.cpp", "jni/Bitmap.cpp", + "jni/HardwareBufferHelpers.cpp", "jni/BitmapFactory.cpp", "jni/ByteBufferStreamAdaptor.cpp", "jni/Camera.cpp", @@ -349,6 +351,8 @@ cc_defaults { "jni/Graphics.cpp", "jni/ImageDecoder.cpp", "jni/Interpolator.cpp", + "jni/MeshSpecification.cpp", + "jni/Mesh.cpp", "jni/MaskFilter.cpp", "jni/NinePatch.cpp", "jni/NinePatchPeeker.cpp", @@ -356,6 +360,7 @@ cc_defaults { "jni/PaintFilter.cpp", "jni/Path.cpp", "jni/PathEffect.cpp", + "jni/PathIterator.cpp", "jni/PathMeasure.cpp", "jni/Picture.cpp", "jni/Region.cpp", @@ -380,6 +385,7 @@ cc_defaults { "external/skia/src/effects", "external/skia/src/image", "external/skia/src/images", + "external/skia/modules/skottie", ], shared_libs: [ @@ -403,9 +409,11 @@ cc_defaults { "jni/AnimatedImageDrawable.cpp", "jni/android_graphics_TextureLayer.cpp", "jni/android_graphics_HardwareRenderer.cpp", + "jni/android_graphics_HardwareBufferRenderer.cpp", "jni/BitmapRegionDecoder.cpp", "jni/GIFMovie.cpp", "jni/GraphicsStatsService.cpp", + "jni/LottieDrawable.cpp", "jni/Movie.cpp", "jni/MovieImpl.cpp", "jni/pdf/PdfDocument.cpp", @@ -513,6 +521,7 @@ cc_defaults { "hwui/BlurDrawLooper.cpp", "hwui/Canvas.cpp", "hwui/ImageDecoder.cpp", + "hwui/LottieDrawable.cpp", "hwui/MinikinSkia.cpp", "hwui/MinikinUtils.cpp", "hwui/PaintImpl.cpp", @@ -529,6 +538,7 @@ cc_defaults { "Interpolator.cpp", "LightingInfo.cpp", "Matrix.cpp", + "MemoryPolicy.cpp", "PathParser.cpp", "Properties.cpp", "PropertyValuesAnimatorSet.cpp", @@ -577,6 +587,7 @@ cc_defaults { "renderthread/VulkanSurface.cpp", "renderthread/RenderProxy.cpp", "renderthread/RenderThread.cpp", + "renderthread/HintSessionWrapper.cpp", "service/GraphicsStatsService.cpp", "thread/CommonPool.cpp", "utils/GLUtils.cpp", @@ -596,6 +607,7 @@ cc_defaults { "ProfileData.cpp", "ProfileDataContainer.cpp", "Readback.cpp", + "Tonemapper.cpp", "TreeInfo.cpp", "WebViewFunctorManager.cpp", "protos/graphicsstats.proto", diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/AndroidTest.xml index 911315f81a8a..75f61f5f7f9d 100644 --- a/libs/hwui/AndroidTest.xml +++ b/libs/hwui/AndroidTest.xml @@ -21,6 +21,7 @@ <option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" /> </target_preparer> <option name="test-suite-tag" value="apct" /> + <option name="not-shardable" value="true" /> <test class="com.android.tradefed.testtype.GTest" > <option name="native-test-device-path" value="/data/local/tmp/nativetest" /> <option name="module-name" value="hwui_unit_tests" /> diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp index d0d24a8738f4..cd4fae86aa52 100644 --- a/libs/hwui/CanvasTransform.cpp +++ b/libs/hwui/CanvasTransform.cpp @@ -15,19 +15,21 @@ */ #include "CanvasTransform.h" -#include "Properties.h" -#include "utils/Color.h" +#include <SkAndroidFrameworkUtils.h> +#include <SkBlendMode.h> #include <SkColorFilter.h> #include <SkGradientShader.h> +#include <SkHighContrastFilter.h> #include <SkPaint.h> #include <SkShader.h> +#include <log/log.h> #include <algorithm> #include <cmath> -#include <log/log.h> -#include <SkHighContrastFilter.h> +#include "Properties.h" +#include "utils/Color.h" namespace android::uirenderer { @@ -82,27 +84,21 @@ static void applyColorTransform(ColorTransform transform, SkPaint& paint) { paint.setColor(newColor); if (paint.getShader()) { - SkShader::GradientInfo info; + SkAndroidFrameworkUtils::LinearGradientInfo info; std::array<SkColor, 10> _colorStorage; std::array<SkScalar, _colorStorage.size()> _offsetStorage; info.fColorCount = _colorStorage.size(); info.fColors = _colorStorage.data(); info.fColorOffsets = _offsetStorage.data(); - SkShader::GradientType type = paint.getShader()->asAGradient(&info); - - if (info.fColorCount <= 10) { - switch (type) { - case SkShader::kLinear_GradientType: - for (int i = 0; i < info.fColorCount; i++) { - info.fColors[i] = transformColor(transform, info.fColors[i]); - } - paint.setShader(SkGradientShader::MakeLinear(info.fPoint, info.fColors, - info.fColorOffsets, info.fColorCount, - info.fTileMode, info.fGradientFlags, nullptr)); - break; - default:break; - } + if (SkAndroidFrameworkUtils::ShaderAsALinearGradient(paint.getShader(), &info) && + info.fColorCount <= _colorStorage.size()) { + for (int i = 0; i < info.fColorCount; i++) { + info.fColors[i] = transformColor(transform, info.fColors[i]); + } + paint.setShader(SkGradientShader::MakeLinear( + info.fPoints, info.fColors, info.fColorOffsets, info.fColorCount, + info.fTileMode, info.fGradientFlags, nullptr)); } } diff --git a/libs/hwui/CopyRequest.h b/libs/hwui/CopyRequest.h new file mode 100644 index 000000000000..5fbd5f900716 --- /dev/null +++ b/libs/hwui/CopyRequest.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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 "Rect.h" +#include "hwui/Bitmap.h" + +namespace android::uirenderer { + +// Keep in sync with PixelCopy.java codes +enum class CopyResult { + Success = 0, + UnknownError = 1, + Timeout = 2, + SourceEmpty = 3, + SourceInvalid = 4, + DestinationInvalid = 5, +}; + +struct CopyRequest { + Rect srcRect; + CopyRequest(Rect srcRect) : srcRect(srcRect) {} + virtual ~CopyRequest() {} + virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) = 0; + virtual void onCopyFinished(CopyResult result) = 0; +}; + +} // namespace android::uirenderer diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h index 9a4c5505fa35..a7f8f6189a8e 100644 --- a/libs/hwui/DeferredLayerUpdater.h +++ b/libs/hwui/DeferredLayerUpdater.h @@ -16,6 +16,7 @@ #pragma once +#include <SkBlendMode.h> #include <SkColorFilter.h> #include <SkImage.h> #include <SkMatrix.h> diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 07594715a84c..32bc122fdc58 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -93,15 +93,25 @@ void DeviceInfo::setWideColorDataspace(ADataSpace dataspace) { case ADATASPACE_SCRGB: get()->mWideColorSpace = SkColorSpace::MakeSRGB(); break; + default: + ALOGW("Unknown dataspace %d", dataspace); + // Treat unknown dataspaces as sRGB, so fall through + [[fallthrough]]; case ADATASPACE_SRGB: // when sRGB is returned, it means wide color gamut is not supported. get()->mWideColorSpace = SkColorSpace::MakeSRGB(); break; - default: - LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); } } +void DeviceInfo::setSupportFp16ForHdr(bool supportFp16ForHdr) { + get()->mSupportFp16ForHdr = supportFp16ForHdr; +} + +void DeviceInfo::setSupportMixedColorSpaces(bool supportMixedColorSpaces) { + get()->mSupportMixedColorSpaces = supportMixedColorSpaces; +} + void DeviceInfo::onRefreshRateChanged(int64_t vsyncPeriod) { mVsyncPeriod = vsyncPeriod; } diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index d5fee3f667a9..d4af0872e31e 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -16,7 +16,9 @@ #ifndef DEVICEINFO_H #define DEVICEINFO_H +#include <SkColorSpace.h> #include <SkImageInfo.h> +#include <SkRefCnt.h> #include <android/data_space.h> #include <mutex> @@ -57,6 +59,12 @@ public: } static void setWideColorDataspace(ADataSpace dataspace); + static void setSupportFp16ForHdr(bool supportFp16ForHdr); + static bool isSupportFp16ForHdr() { return get()->mSupportFp16ForHdr; }; + + static void setSupportMixedColorSpaces(bool supportMixedColorSpaces); + static bool isSupportMixedColorSpaces() { return get()->mSupportMixedColorSpaces; }; + // this value is only valid after the GPU has been initialized and there is a valid graphics // context or if you are using the HWUI_NULL_GPU int maxTextureSize() const; @@ -86,6 +94,8 @@ private: int mMaxTextureSize; sk_sp<SkColorSpace> mWideColorSpace = SkColorSpace::MakeSRGB(); + bool mSupportFp16ForHdr = false; + bool mSupportMixedColorSpaces = false; SkColorType mWideColorType = SkColorType::kN32_SkColorType; int mDisplaysSize = 0; int mPhysicalDisplayIndex = -1; diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in index 4ec782f6fec0..e2127efca716 100644 --- a/libs/hwui/DisplayListOps.in +++ b/libs/hwui/DisplayListOps.in @@ -52,3 +52,4 @@ X(DrawShadowRec) X(DrawVectorDrawable) X(DrawRippleDrawable) X(DrawWebView) +X(DrawMesh) diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h index 564ee4f53a54..b15b6cb9a9ec 100644 --- a/libs/hwui/FrameInfo.h +++ b/libs/hwui/FrameInfo.h @@ -104,6 +104,7 @@ public: set(FrameInfoIndex::AnimationStart) = vsyncTime; set(FrameInfoIndex::PerformTraversalsStart) = vsyncTime; set(FrameInfoIndex::DrawStart) = vsyncTime; + set(FrameInfoIndex::FrameStartTime) = vsyncTime; set(FrameInfoIndex::FrameDeadline) = frameDeadline; set(FrameInfoIndex::FrameInterval) = frameInterval; return *this; diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index c24cabb287de..b7e99994355c 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -22,8 +22,11 @@ #include <GLES2/gl2ext.h> #include <GLES3/gl3.h> #include <GrDirectContext.h> +#include <SkBitmap.h> #include <SkCanvas.h> #include <SkImage.h> +#include <SkImageInfo.h> +#include <SkRefCnt.h> #include <gui/TraceUtils.h> #include <utils/GLUtils.h> #include <utils/NdkUtils.h> @@ -39,6 +42,8 @@ namespace android::uirenderer { +static constexpr auto kThreadTimeout = 60000_ms; + class AHBUploader; // This helper uploader classes allows us to upload using either EGL or Vulkan using the same // interface. @@ -77,7 +82,7 @@ public: } void postIdleTimeoutCheck() { - mUploadThread->queue().postDelayed(5000_ms, [this](){ this->idleTimeoutCheck(); }); + mUploadThread->queue().postDelayed(kThreadTimeout, [this]() { this->idleTimeoutCheck(); }); } protected: @@ -94,7 +99,7 @@ private: bool shouldTimeOutLocked() { nsecs_t durationSince = systemTime() - mLastUpload; - return durationSince > 2000_ms; + return durationSince > kThreadTimeout; } void idleTimeoutCheck() { diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h index 81057a24c29c..00ee99648889 100644 --- a/libs/hwui/HardwareBitmapUploader.h +++ b/libs/hwui/HardwareBitmapUploader.h @@ -17,6 +17,9 @@ #pragma once #include <hwui/Bitmap.h> +#include <SkRefCnt.h> + +class SkBitmap; namespace android::uirenderer { diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 9053c1240957..fc3118ae32dd 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -20,6 +20,8 @@ #include "utils/Color.h" #include "utils/MathUtils.h" +#include <SkBlendMode.h> + #include <log/log.h> namespace android { diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp new file mode 100644 index 000000000000..ca1312e75f4c --- /dev/null +++ b/libs/hwui/MemoryPolicy.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "MemoryPolicy.h" + +#include <android-base/properties.h> + +#include <optional> +#include <string_view> + +#include "Properties.h" + +namespace android::uirenderer { + +constexpr static MemoryPolicy sDefaultMemoryPolicy; +constexpr static MemoryPolicy sPersistentOrSystemPolicy{ + .contextTimeout = 10_s, + .useAlternativeUiHidden = true, +}; +constexpr static MemoryPolicy sLowRamPolicy{ + .useAlternativeUiHidden = true, + .purgeScratchOnly = false, +}; +constexpr static MemoryPolicy sExtremeLowRam{ + .initialMaxSurfaceAreaScale = 0.2f, + .surfaceSizeMultiplier = 5 * 4.0f, + .backgroundRetentionPercent = 0.2f, + .contextTimeout = 5_s, + .minimumResourceRetention = 1_s, + .useAlternativeUiHidden = true, + .purgeScratchOnly = false, + .releaseContextOnStoppedOnly = true, +}; + +const MemoryPolicy& loadMemoryPolicy() { + if (Properties::isSystemOrPersistent) { + return sPersistentOrSystemPolicy; + } + std::string memoryPolicy = base::GetProperty(PROPERTY_MEMORY_POLICY, ""); + if (memoryPolicy == "default") { + return sDefaultMemoryPolicy; + } + if (memoryPolicy == "lowram") { + return sLowRamPolicy; + } + if (memoryPolicy == "extremelowram") { + return sExtremeLowRam; + } + + if (Properties::isLowRam) { + return sLowRamPolicy; + } + return sDefaultMemoryPolicy; +} + +} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h new file mode 100644 index 000000000000..2f0f7f506447 --- /dev/null +++ b/libs/hwui/MemoryPolicy.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 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 "utils/TimeUtils.h" + +namespace android::uirenderer { + +// Values mirror those from ComponentCallbacks2.java +enum class TrimLevel { + COMPLETE = 80, + MODERATE = 60, + BACKGROUND = 40, + UI_HIDDEN = 20, + RUNNING_CRITICAL = 15, + RUNNING_LOW = 10, + RUNNING_MODERATE = 5, +}; + +struct MemoryPolicy { + // The initial scale factor applied to the display resolution. The default is 1, but + // lower values may be used to start with a smaller initial cache size. The cache will + // be adjusted if larger frames are actually rendered + float initialMaxSurfaceAreaScale = 1.0f; + // The foreground cache size multiplier. The surface area of the screen will be multiplied + // by this + float surfaceSizeMultiplier = 12.0f * 4.0f; + // How much of the foreground cache size should be preserved when going into the background + float backgroundRetentionPercent = 0.5f; + // How long after the last renderer goes away before the GPU context is released. A value + // of 0 means only drop the context on background TRIM signals + nsecs_t contextTimeout = 10_s; + // The minimum amount of time to hold onto items in the resource cache + // The actual time used will be the max of this & when frames were actually rendered + nsecs_t minimumResourceRetention = 10_s; + // If false, use only TRIM_UI_HIDDEN to drive background cache limits; + // If true, use all signals (such as all contexts are stopped) to drive the limits + bool useAlternativeUiHidden = true; + // Whether or not to only purge scratch resources when triggering UI Hidden or background + // collection + bool purgeScratchOnly = true; + // Whether or not to trigger releasing GPU context when all contexts are stopped + bool releaseContextOnStoppedOnly = true; +}; + +const MemoryPolicy& loadMemoryPolicy(); + +} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 5a67eb9935dd..6affc6a81685 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -82,11 +82,15 @@ bool Properties::isolatedProcess = false; int Properties::contextPriority = 0; float Properties::defaultSdrWhitePoint = 200.f; -bool Properties::useHintManager = true; +bool Properties::useHintManager = false; int Properties::targetCpuTimePercentage = 70; bool Properties::enableWebViewOverlays = true; +bool Properties::isHighEndGfx = true; +bool Properties::isLowRam = false; +bool Properties::isSystemOrPersistent = false; + StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI; DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized; @@ -138,7 +142,7 @@ bool Properties::load() { runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false); - useHintManager = base::GetBoolProperty(PROPERTY_USE_HINT_MANAGER, true); + useHintManager = base::GetBoolProperty(PROPERTY_USE_HINT_MANAGER, false); targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70); if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70; diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 2f8c67903a8b..96a517629eaa 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -193,6 +193,8 @@ enum DebugLevel { */ #define PROPERTY_DRAWING_ENABLED "debug.hwui.drawing_enabled" +#define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy" + /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -292,16 +294,27 @@ public: static bool enableWebViewOverlays; + static bool isHighEndGfx; + static bool isLowRam; + static bool isSystemOrPersistent; + static StretchEffectBehavior getStretchEffectBehavior() { return stretchEffectBehavior; } static void setIsHighEndGfx(bool isHighEndGfx) { + Properties::isHighEndGfx = isHighEndGfx; stretchEffectBehavior = isHighEndGfx ? StretchEffectBehavior::ShaderHWUI : StretchEffectBehavior::UniformScale; } + static void setIsLowRam(bool isLowRam) { Properties::isLowRam = isLowRam; } + + static void setIsSystemOrPersistent(bool isSystemOrPersistent) { + Properties::isSystemOrPersistent = isSystemOrPersistent; + } + /** * Used for testing. Typical configuration of stretch behavior is done * through setIsHighEndGfx diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index a3ba88e4ee8a..045de35c1d97 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -16,12 +16,28 @@ #include "Readback.h" +#include <SkBitmap.h> +#include <SkBlendMode.h> +#include <SkCanvas.h> +#include <SkColorSpace.h> +#include <SkImage.h> +#include <SkImageInfo.h> +#include <SkMatrix.h> +#include <SkPaint.h> +#include <SkRect.h> +#include <SkRefCnt.h> +#include <SkSamplingOptions.h> +#include <SkSurface.h> +#include "include/gpu/GpuTypes.h" // from Skia +#include <gui/TraceUtils.h> +#include <private/android/AHardwareBufferHelpers.h> +#include <shaders/shaders.h> #include <sync/sync.h> #include <system/window.h> -#include <gui/TraceUtils.h> #include "DeferredLayerUpdater.h" #include "Properties.h" +#include "Tonemapper.h" #include "hwui/Bitmap.h" #include "pipeline/skia/LayerDrawable.h" #include "renderthread/EglManager.h" @@ -37,8 +53,7 @@ namespace uirenderer { #define ARECT_ARGS(r) float((r).left), float((r).top), float((r).right), float((r).bottom) -CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRect, - SkBitmap* bitmap) { +void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<CopyRequest>& request) { ATRACE_CALL(); // Setup the source AHardwareBuffer* rawSourceBuffer; @@ -51,44 +66,57 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec // Really this shouldn't ever happen, but better safe than sorry. if (err == UNKNOWN_TRANSACTION) { ALOGW("Readback failed to ANativeWindow_getLastQueuedBuffer2 - who are we talking to?"); - return copySurfaceIntoLegacy(window, inSrcRect, bitmap); + return request->onCopyFinished(CopyResult::SourceInvalid); } ALOGV("Using new path, cropRect=" RECT_STRING ", transform=%x", ARECT_ARGS(cropRect), windowTransform); if (err != NO_ERROR) { ALOGW("Failed to get last queued buffer, error = %d", err); - return CopyResult::UnknownError; + return request->onCopyFinished(CopyResult::SourceInvalid); } if (rawSourceBuffer == nullptr) { ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); - return CopyResult::SourceEmpty; + return request->onCopyFinished(CopyResult::SourceEmpty); } UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer}; AHardwareBuffer_Desc description; AHardwareBuffer_describe(sourceBuffer.get(), &description); if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) { ALOGW("Surface is protected, unable to copy from it"); - return CopyResult::SourceInvalid; + return request->onCopyFinished(CopyResult::SourceInvalid); } - if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) { - ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); - return CopyResult::Timeout; + { + ATRACE_NAME("sync_wait"); + if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) { + ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); + return request->onCopyFinished(CopyResult::Timeout); + } } - sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace( - static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window))); + int32_t dataspace = ANativeWindow_getBuffersDataSpace(window); + + // If the application is not updating the Surface themselves, e.g., another + // process is producing buffers for the application to display, then + // ANativeWindow_getBuffersDataSpace will return an unknown answer, so grab + // the dataspace from buffer metadata instead, if it exists. + if (dataspace == 0) { + dataspace = AHardwareBuffer_getDataSpace(sourceBuffer.get()); + } + + sk_sp<SkColorSpace> colorSpace = + DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace)); sk_sp<SkImage> image = SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace); if (!image.get()) { - return CopyResult::UnknownError; + return request->onCopyFinished(CopyResult::UnknownError); } sk_sp<GrDirectContext> grContext = mRenderThread.requireGrContext(); - SkRect srcRect = inSrcRect.toSkRect(); + SkRect srcRect = request->srcRect.toSkRect(); SkRect imageSrcRect = SkRect::MakeIWH(description.width, description.height); SkISize imageWH = SkISize::Make(description.width, description.height); @@ -136,23 +164,26 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec ALOGV("intersecting " RECT_STRING " with " RECT_STRING, SK_RECT_ARGS(srcRect), SK_RECT_ARGS(textureRect)); if (!srcRect.intersect(textureRect)) { - return CopyResult::UnknownError; + return request->onCopyFinished(CopyResult::UnknownError); } } + SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height()); + SkBitmap* bitmap = &skBitmap; sk_sp<SkSurface> tmpSurface = - SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, + SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes, bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr); // if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we // attempt to do the intermediate rendering step in 8888 if (!tmpSurface.get()) { SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType); - tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, + tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), + skgpu::Budgeted::kYes, tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr); if (!tmpSurface.get()) { ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap"); - return CopyResult::UnknownError; + return request->onCopyFinished(CopyResult::UnknownError); } } @@ -211,6 +242,10 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec const bool hasBufferCrop = cropRect.left < cropRect.right && cropRect.top < cropRect.bottom; auto constraint = hasBufferCrop ? SkCanvas::kStrict_SrcRectConstraint : SkCanvas::kFast_SrcRectConstraint; + + static constexpr float kMaxLuminanceNits = 4000.f; + tonemapPaint(image->imageInfo(), canvas->imageInfo(), kMaxLuminanceNits, paint); + canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint, constraint); canvas->restore(); @@ -223,52 +258,13 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec !tmpBitmap.tryAllocPixels(tmpInfo) || !tmpSurface->readPixels(tmpBitmap, 0, 0) || !tmpBitmap.readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) { ALOGW("Unable to convert content into the provided bitmap"); - return CopyResult::UnknownError; + return request->onCopyFinished(CopyResult::UnknownError); } } bitmap->notifyPixelsChanged(); - return CopyResult::Success; -} - -CopyResult Readback::copySurfaceIntoLegacy(ANativeWindow* window, const Rect& srcRect, - SkBitmap* bitmap) { - // Setup the source - AHardwareBuffer* rawSourceBuffer; - int rawSourceFence; - Matrix4 texTransform; - status_t err = ANativeWindow_getLastQueuedBuffer(window, &rawSourceBuffer, &rawSourceFence, - texTransform.data); - base::unique_fd sourceFence(rawSourceFence); - texTransform.invalidateType(); - if (err != NO_ERROR) { - ALOGW("Failed to get last queued buffer, error = %d", err); - return CopyResult::UnknownError; - } - if (rawSourceBuffer == nullptr) { - ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); - return CopyResult::SourceEmpty; - } - - UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer}; - AHardwareBuffer_Desc description; - AHardwareBuffer_describe(sourceBuffer.get(), &description); - if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) { - ALOGW("Surface is protected, unable to copy from it"); - return CopyResult::SourceInvalid; - } - - if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) { - ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); - return CopyResult::Timeout; - } - - sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace( - static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window))); - sk_sp<SkImage> image = - SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace); - return copyImageInto(image, srcRect, bitmap); + return request->onCopyFinished(CopyResult::Success); } CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) { @@ -306,14 +302,14 @@ CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect, SkBitmap* bitmap) { ATRACE_CALL(); + if (!image.get()) { + return CopyResult::UnknownError; + } if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { mRenderThread.requireGlContext(); } else { mRenderThread.requireVkContext(); } - if (!image.get()) { - return CopyResult::UnknownError; - } int imgWidth = image->width(); int imgHeight = image->height(); sk_sp<GrDirectContext> grContext = sk_ref_sp(mRenderThread.getGrContext()); @@ -351,14 +347,17 @@ bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* * software buffer. */ sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), - SkBudgeted::kYes, bitmap->info(), 0, + skgpu::Budgeted::kYes, + bitmap->info(), + 0, kTopLeft_GrSurfaceOrigin, nullptr); // if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we // attempt to do the intermediate rendering step in 8888 if (!tmpSurface.get()) { SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType); - tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, + tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), + skgpu::Budgeted::kYes, tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr); if (!tmpSurface.get()) { ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap"); diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h index d0d748ff5c16..a092d472abf0 100644 --- a/libs/hwui/Readback.h +++ b/libs/hwui/Readback.h @@ -16,11 +16,16 @@ #pragma once +#include <SkRefCnt.h> + +#include "CopyRequest.h" #include "Matrix.h" #include "Rect.h" #include "renderthread/RenderThread.h" -#include <SkBitmap.h> +class SkBitmap; +class SkImage; +struct SkRect; namespace android { class Bitmap; @@ -31,23 +36,13 @@ namespace uirenderer { class DeferredLayerUpdater; class Layer; -// Keep in sync with PixelCopy.java codes -enum class CopyResult { - Success = 0, - UnknownError = 1, - Timeout = 2, - SourceEmpty = 3, - SourceInvalid = 4, - DestinationInvalid = 5, -}; - class Readback { public: explicit Readback(renderthread::RenderThread& thread) : mRenderThread(thread) {} /** * Copies the surface's most recently queued buffer into the provided bitmap. */ - CopyResult copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap); + void copySurfaceInto(ANativeWindow* window, const std::shared_ptr<CopyRequest>& request); CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap); CopyResult copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap); @@ -55,7 +50,6 @@ public: CopyResult copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); private: - CopyResult copySurfaceIntoLegacy(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap); CopyResult copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect, SkBitmap* bitmap); bool copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect, diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index a285462eef74..e1030b0faf8e 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -15,13 +15,16 @@ */ #include "RecordingCanvas.h" -#include <hwui/Paint.h> #include <GrRecordingContext.h> +#include <SkMesh.h> +#include <hwui/Paint.h> #include <experimental/type_traits> +#include <utility> #include "SkAndroidFrameworkUtils.h" +#include "SkBlendMode.h" #include "SkCanvas.h" #include "SkCanvasPriv.h" #include "SkColor.h" @@ -29,14 +32,19 @@ #include "SkDrawShadowInfo.h" #include "SkImage.h" #include "SkImageFilter.h" +#include "SkImageInfo.h" #include "SkLatticeIter.h" #include "SkMath.h" +#include "SkPaint.h" #include "SkPicture.h" +#include "SkRRect.h" #include "SkRSXform.h" +#include "SkRect.h" #include "SkRegion.h" #include "SkTextBlob.h" #include "SkVertices.h" #include "VectorDrawable.h" +#include "include/gpu/GpuTypes.h" // from Skia #include "pipeline/skia/AnimatedDrawables.h" #include "pipeline/skia/FunctorDrawable.h" @@ -266,7 +274,6 @@ struct DrawDRRect final : Op { SkPaint paint; void draw(SkCanvas* c, const SkMatrix&) const { c->drawDRRect(outer, inner, paint); } }; - struct DrawAnnotation final : Op { static const auto kType = Type::DrawAnnotation; DrawAnnotation(const SkRect& rect, SkData* value) : rect(rect), value(sk_ref_sp(value)) {} @@ -448,6 +455,16 @@ struct DrawVertices final : Op { c->drawVertices(vertices, mode, paint); } }; +struct DrawMesh final : Op { + static const auto kType = Type::DrawMesh; + DrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) + : mesh(mesh), blender(std::move(blender)), paint(paint) {} + + SkMesh mesh; + sk_sp<SkBlender> blender; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { c->drawMesh(mesh, blender, paint); } +}; struct DrawAtlas final : Op { static const auto kType = Type::DrawAtlas; DrawAtlas(const SkImage* atlas, int count, SkBlendMode mode, const SkSamplingOptions& sampling, @@ -554,7 +571,7 @@ public: GrRecordingContext* directContext = c->recordingContext(); mLayerImageInfo = c->imageInfo().makeWH(deviceBounds.width(), deviceBounds.height()); - mLayerSurface = SkSurface::MakeRenderTarget(directContext, SkBudgeted::kYes, + mLayerSurface = SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes, mLayerImageInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr); } @@ -589,12 +606,17 @@ public: }; } +static constexpr inline bool is_power_of_two(int value) { + return (value & (value - 1)) == 0; +} + template <typename T, typename... Args> void* DisplayListData::push(size_t pod, Args&&... args) { size_t skip = SkAlignPtr(sizeof(T) + pod); SkASSERT(skip < (1 << 24)); if (fUsed + skip > fReserved) { - static_assert(SkIsPow2(SKLITEDL_PAGE), "This math needs updating for non-pow2."); + static_assert(is_power_of_two(SKLITEDL_PAGE), + "This math needs updating for non-pow2."); // Next greater multiple of SKLITEDL_PAGE. fReserved = (fUsed + skip + SKLITEDL_PAGE) & ~(SKLITEDL_PAGE - 1); fBytes.realloc(fReserved); @@ -759,6 +781,10 @@ void DisplayListData::drawPoints(SkCanvas::PointMode mode, size_t count, const S void DisplayListData::drawVertices(const SkVertices* vert, SkBlendMode mode, const SkPaint& paint) { this->push<DrawVertices>(0, vert, mode, paint); } +void DisplayListData::drawMesh(const SkMesh& mesh, const sk_sp<SkBlender>& blender, + const SkPaint& paint) { + this->push<DrawMesh>(0, mesh, blender, paint); +} void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[], const SkColor colors[], int count, SkBlendMode xfermode, const SkSamplingOptions& sampling, const SkRect* cull, @@ -1101,6 +1127,10 @@ void RecordingCanvas::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) { fDL->drawVertices(vertices, mode, paint); } +void RecordingCanvas::onDrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, + const SkPaint& paint) { + fDL->drawMesh(mesh, blender, paint); +} void RecordingCanvas::onDrawAtlas2(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[], const SkColor colors[], int count, SkBlendMode bmode, const SkSamplingOptions& sampling, diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 212b4e72dcb2..b7d4dc90f429 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -16,11 +16,13 @@ #pragma once -#include "CanvasTransform.h" -#include "hwui/Bitmap.h" -#include "utils/Macros.h" -#include "utils/TypeLogic.h" +#include <SkRuntimeEffect.h> +#include <log/log.h> + +#include <cstdlib> +#include <vector> +#include "CanvasTransform.h" #include "SkCanvas.h" #include "SkCanvasVirtualEnforcer.h" #include "SkDrawable.h" @@ -28,11 +30,14 @@ #include "SkPaint.h" #include "SkPath.h" #include "SkRect.h" - +#include "hwui/Bitmap.h" #include "pipeline/skia/AnimatedDrawables.h" +#include "utils/AutoMalloc.h" +#include "utils/Macros.h" +#include "utils/TypeLogic.h" -#include <SkRuntimeEffect.h> -#include <vector> +enum class SkBlendMode; +class SkRRect; namespace android { namespace uirenderer { @@ -109,6 +114,8 @@ private: void drawRRect(const SkRRect&, const SkPaint&); void drawDRRect(const SkRRect&, const SkRRect&, const SkPaint&); + void drawMesh(const SkMesh&, const sk_sp<SkBlender>&, const SkPaint&); + void drawAnnotation(const SkRect&, const char*, SkData*); void drawDrawable(SkDrawable*, const SkMatrix*); void drawPicture(const SkPicture*, const SkMatrix*, const SkPaint*); @@ -140,7 +147,7 @@ private: template <typename Fn, typename... Args> void map(const Fn[], Args...) const; - SkAutoTMalloc<uint8_t> fBytes; + AutoTMalloc<uint8_t> fBytes; size_t fUsed = 0; size_t fReserved = 0; @@ -208,6 +215,7 @@ public: const SkPaint&) override; void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override; void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override; + void onDrawMesh(const SkMesh&, sk_sp<SkBlender>, const SkPaint&) override; void onDrawAtlas2(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int, SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*) override; void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override; diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index 24443c8c9836..7170226037f9 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -71,9 +71,14 @@ public: , right(rect.fRight) , bottom(rect.fBottom) {} - friend int operator==(const Rect& a, const Rect& b) { return !memcmp(&a, &b, sizeof(a)); } + friend int operator==(const Rect& a, const Rect& b) { + return a.left == b.left && + a.top == b.top && + a.right == b.right && + a.bottom == b.bottom; + } - friend int operator!=(const Rect& a, const Rect& b) { return memcmp(&a, &b, sizeof(a)); } + friend int operator!=(const Rect& a, const Rect& b) { return !(a == b); } inline void clear() { left = top = right = bottom = 0.0f; } diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index da0476259b97..bdc48e91f6cb 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -16,7 +16,6 @@ #pragma once -#include <SkCamera.h> #include <SkMatrix.h> #include <utils/LinearAllocator.h> diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index f9b3a8c12b2e..af8bd263f97d 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -16,28 +16,24 @@ #include "SkiaCanvas.h" -#include "CanvasProperty.h" -#include "NinePatchUtils.h" -#include "VectorDrawable.h" -#include "hwui/Bitmap.h" -#include "hwui/MinikinUtils.h" -#include "hwui/PaintFilter.h" -#include "pipeline/skia/AnimatedDrawables.h" -#include "pipeline/skia/HolePunch.h" - #include <SkAndroidFrameworkUtils.h> #include <SkAnimatedImage.h> +#include <SkBitmap.h> #include <SkCanvasPriv.h> #include <SkCanvasStateUtils.h> #include <SkColorFilter.h> -#include <SkDeque.h> #include <SkDrawable.h> #include <SkFont.h> #include <SkGraphics.h> #include <SkImage.h> #include <SkImagePriv.h> +#include <SkMatrix.h> +#include <SkPaint.h> #include <SkPicture.h> +#include <SkRRect.h> #include <SkRSXform.h> +#include <SkRect.h> +#include <SkRefCnt.h> #include <SkShader.h> #include <SkTemplates.h> #include <SkTextBlob.h> @@ -47,6 +43,16 @@ #include <optional> #include <utility> +#include "CanvasProperty.h" +#include "NinePatchUtils.h" +#include "SkBlendMode.h" +#include "VectorDrawable.h" +#include "hwui/Bitmap.h" +#include "hwui/MinikinUtils.h" +#include "hwui/PaintFilter.h" +#include "pipeline/skia/AnimatedDrawables.h" +#include "pipeline/skia/HolePunch.h" + namespace android { using uirenderer::PaintUtils; @@ -169,7 +175,7 @@ int SkiaCanvas::save(SaveFlags::Flags flags) { // operation. It does this by explicitly saving off the clip & matrix state // when requested and playing it back after the SkCanvas::restore. void SkiaCanvas::restore() { - const auto* rec = this->currentSaveRec(); + const SaveRec* rec = this->currentSaveRec(); if (!rec) { // Fast path - no record for this frame. mCanvas->restore(); @@ -238,17 +244,20 @@ void SkiaCanvas::restoreUnclippedLayer(int restoreCount, const Paint& paint) { } const SkiaCanvas::SaveRec* SkiaCanvas::currentSaveRec() const { - const SaveRec* rec = mSaveStack ? static_cast<const SaveRec*>(mSaveStack->back()) : nullptr; + const SaveRec* rec = (mSaveStack && !mSaveStack->empty()) + ? static_cast<const SaveRec*>(&mSaveStack->back()) + : nullptr; int currentSaveCount = mCanvas->getSaveCount(); SkASSERT(!rec || currentSaveCount >= rec->saveCount); return (rec && rec->saveCount == currentSaveCount) ? rec : nullptr; } -void SkiaCanvas::punchHole(const SkRRect& rect) { +void SkiaCanvas::punchHole(const SkRRect& rect, float alpha) { SkPaint paint = SkPaint(); - paint.setColor(0); - paint.setBlendMode(SkBlendMode::kClear); + paint.setColor(SkColors::kBlack); + paint.setAlphaf(alpha); + paint.setBlendMode(SkBlendMode::kDstOut); mCanvas->drawRRect(rect, paint); } @@ -269,13 +278,12 @@ void SkiaCanvas::recordPartialSave(SaveFlags::Flags flags) { } if (!mSaveStack) { - mSaveStack.reset(new SkDeque(sizeof(struct SaveRec), 8)); + mSaveStack.reset(new std::deque<SaveRec>()); } - SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back()); - rec->saveCount = mCanvas->getSaveCount(); - rec->saveFlags = flags; - rec->clipIndex = mClipStack.size(); + mSaveStack->emplace_back(mCanvas->getSaveCount(), // saveCount + flags, // saveFlags + mClipStack.size()); // clipIndex } template <typename T> @@ -306,7 +314,7 @@ void SkiaCanvas::applyPersistentClips(size_t clipStartIndex) { // If the current/post-restore save rec is also persisting clips, we // leave them on the stack to be reapplied part of the next restore(). // Otherwise we're done and just pop them. - const auto* rec = this->currentSaveRec(); + const SaveRec* rec = this->currentSaveRec(); if (!rec || (rec->saveFlags & SaveFlags::Clip)) { mClipStack.erase(begin, end); } @@ -562,6 +570,10 @@ void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, cons applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawVertices(vertices, mode, p); }); } +void SkiaCanvas::drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) { + mCanvas->drawMesh(mesh, blender, paint); +} + // ---------------------------------------------------------------------------- // Canvas draw operations: Bitmaps // ---------------------------------------------------------------------------- @@ -724,6 +736,10 @@ double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) { return imgDrawable->drawStaging(mCanvas); } +void SkiaCanvas::drawLottie(LottieDrawable* lottieDrawable) { + lottieDrawable->drawStaging(mCanvas); +} + void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) { vectorDrawable->drawStaging(this); } diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 715007cdcd3b..1524dff739d5 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -19,19 +19,21 @@ #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration #include "DeferredLayerUpdater.h" #endif +#include <SkCanvas.h> + +#include <cassert> +#include <deque> +#include <optional> + #include "RenderNode.h" #include "VectorDrawable.h" +#include "hwui/BlurDrawLooper.h" #include "hwui/Canvas.h" #include "hwui/Paint.h" -#include "hwui/BlurDrawLooper.h" - -#include <SkCanvas.h> -#include <SkDeque.h> #include "pipeline/skia/AnimatedDrawables.h" -#include "src/core/SkArenaAlloc.h" -#include <cassert> -#include <optional> +enum class SkBlendMode; +class SkRRect; namespace android { @@ -63,7 +65,7 @@ public: LOG_ALWAYS_FATAL("SkiaCanvas does not support enableZ"); } - virtual void punchHole(const SkRRect& rect) override; + virtual void punchHole(const SkRRect& rect, float alpha) override; virtual void setBitmap(const SkBitmap& bitmap) override; @@ -117,8 +119,8 @@ public: virtual void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const Paint& paint) override; - virtual void drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner, - const Paint& paint) override; + virtual void drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner, + const Paint& paint) override; virtual void drawCircle(float x, float y, float radius, const Paint& paint) override; virtual void drawOval(float left, float top, float right, float bottom, @@ -127,6 +129,8 @@ public: float sweepAngle, bool useCenter, const Paint& paint) override; virtual void drawPath(const SkPath& path, const Paint& paint) override; virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) override; + virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, + const SkPaint& paint) override; virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) override; virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) override; @@ -140,6 +144,7 @@ public: float dstTop, float dstRight, float dstBottom, const Paint* paint) override; virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) override; + virtual void drawLottie(LottieDrawable* lottieDrawable) override; virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; @@ -206,6 +211,9 @@ private: int saveCount; SaveFlags::Flags saveFlags; size_t clipIndex; + + SaveRec(int saveCount, SaveFlags::Flags saveFlags, size_t clipIndex) + : saveCount(saveCount), saveFlags(saveFlags), clipIndex(clipIndex) {} }; const SaveRec* currentSaveRec() const; @@ -219,11 +227,11 @@ private: class Clip; - std::unique_ptr<SkCanvas> mCanvasOwned; // might own a canvas we allocated - SkCanvas* mCanvas; // we do NOT own this canvas, it must survive us - // unless it is the same as mCanvasOwned.get() - std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves. - std::vector<Clip> mClipStack; // tracks persistent clips. + std::unique_ptr<SkCanvas> mCanvasOwned; // Might own a canvas we allocated. + SkCanvas* mCanvas; // We do NOT own this canvas, it must survive us + // unless it is the same as mCanvasOwned.get(). + std::unique_ptr<std::deque<SaveRec>> mSaveStack; // Lazily allocated, tracks partial saves. + std::vector<Clip> mClipStack; // Tracks persistent clips. sk_sp<PaintFilter> mPaintFilter; }; diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp index 0695dd1ab218..47bd0b96ebd3 100644 --- a/libs/hwui/SkiaInterpolator.cpp +++ b/libs/hwui/SkiaInterpolator.cpp @@ -17,11 +17,13 @@ #include "SkiaInterpolator.h" #include "include/core/SkMath.h" +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" #include "include/private/SkFixed.h" -#include "include/private/SkMalloc.h" -#include "include/private/SkTo.h" #include "src/core/SkTSearch.h" +#include <log/log.h> + typedef int Dot14; #define Dot14_ONE (1 << 14) #define Dot14_HALF (1 << 13) @@ -91,25 +93,23 @@ static float SkUnitCubicInterp(float value, float bx, float by, float cx, float SkiaInterpolatorBase::SkiaInterpolatorBase() { fStorage = nullptr; fTimes = nullptr; - SkDEBUGCODE(fTimesArray = nullptr;) } SkiaInterpolatorBase::~SkiaInterpolatorBase() { if (fStorage) { - sk_free(fStorage); + free(fStorage); } } void SkiaInterpolatorBase::reset(int elemCount, int frameCount) { fFlags = 0; - fElemCount = SkToU8(elemCount); - fFrameCount = SkToS16(frameCount); + fElemCount = static_cast<uint8_t>(elemCount); + fFrameCount = static_cast<int16_t>(frameCount); fRepeat = SK_Scalar1; if (fStorage) { - sk_free(fStorage); + free(fStorage); fStorage = nullptr; fTimes = nullptr; - SkDEBUGCODE(fTimesArray = nullptr); } } @@ -205,7 +205,6 @@ SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T SkiaInterpolator::SkiaInterpolator() { INHERITED::reset(0, 0); fValues = nullptr; - SkDEBUGCODE(fScalarsArray = nullptr;) } SkiaInterpolator::SkiaInterpolator(int elemCount, int frameCount) { @@ -215,13 +214,12 @@ SkiaInterpolator::SkiaInterpolator(int elemCount, int frameCount) { void SkiaInterpolator::reset(int elemCount, int frameCount) { INHERITED::reset(elemCount, frameCount); - fStorage = sk_malloc_throw((sizeof(float) * elemCount + sizeof(SkTimeCode)) * frameCount); + size_t numBytes = (sizeof(float) * elemCount + sizeof(SkTimeCode)) * frameCount; + fStorage = malloc(numBytes); + LOG_ALWAYS_FATAL_IF(!fStorage, "Failed to allocate %zu bytes in %s", + numBytes, __func__); fTimes = (SkTimeCode*)fStorage; fValues = (float*)((char*)fStorage + sizeof(SkTimeCode) * frameCount); -#ifdef SK_DEBUG - fTimesArray = (SkTimeCode(*)[10])fTimes; - fScalarsArray = (float(*)[10])fValues; -#endif } #define SK_Fixed1Third (SK_Fixed1 / 3) diff --git a/libs/hwui/SkiaInterpolator.h b/libs/hwui/SkiaInterpolator.h index c03f502528be..9422cb526a8f 100644 --- a/libs/hwui/SkiaInterpolator.h +++ b/libs/hwui/SkiaInterpolator.h @@ -17,7 +17,8 @@ #ifndef SkiaInterpolator_DEFINED #define SkiaInterpolator_DEFINED -#include "include/private/SkTo.h" +#include <cstddef> +#include <cstdint> class SkiaInterpolatorBase { public: @@ -46,7 +47,9 @@ public: @param mirror If true, the odd repeats interpolate from the last key frame and the first. */ - void setMirror(bool mirror) { fFlags = SkToU8((fFlags & ~kMirror) | (int)mirror); } + void setMirror(bool mirror) { + fFlags = static_cast<uint8_t>((fFlags & ~kMirror) | (int)mirror); + } /** Set the repeat count. The repeat count may be fractional. @param repeatCount Multiplies the total time by this scalar. @@ -57,7 +60,7 @@ public: @param reset If true, the odd repeats interpolate from the last key frame and the first. */ - void setReset(bool reset) { fFlags = SkToU8((fFlags & ~kReset) | (int)reset); } + void setReset(bool reset) { fFlags = static_cast<uint8_t>((fFlags & ~kReset) | (int)reset); } Result timeToT(uint32_t time, float* T, int* index, bool* exact) const; @@ -75,9 +78,6 @@ protected: }; SkTimeCode* fTimes; // pointer into fStorage void* fStorage; -#ifdef SK_DEBUG - SkTimeCode (*fTimesArray)[10]; -#endif }; class SkiaInterpolator : public SkiaInterpolatorBase { diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp new file mode 100644 index 000000000000..a7e76b631140 --- /dev/null +++ b/libs/hwui/Tonemapper.cpp @@ -0,0 +1,107 @@ +/* + * Copyright 2022 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. + */ + +#include "Tonemapper.h" + +#include <SkRuntimeEffect.h> +#include <log/log.h> +#include <shaders/shaders.h> + +#include "utils/Color.h" + +namespace android::uirenderer { + +namespace { + +class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder { +public: + explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect) + : SkRuntimeEffectBuilder(std::move(effect)) {} + + sk_sp<SkColorFilter> makeColorFilter() { + return this->effect()->makeColorFilter(this->uniforms()); + } +}; + +static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearEffect& linearEffect, + float maxDisplayLuminance, + float currentDisplayLuminanceNits, + float maxLuminance) { + auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect)); + auto [runtimeEffect, error] = SkRuntimeEffect::MakeForColorFilter(std::move(shaderString)); + if (!runtimeEffect) { + LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str()); + } + + ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect)); + + const auto uniforms = + shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance, + currentDisplayLuminanceNits, maxLuminance); + + for (const auto& uniform : uniforms) { + effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size()); + } + + return effectBuilder.makeColorFilter(); +} + +static bool extractTransfer(ui::Dataspace dataspace) { + return dataspace & HAL_DATASPACE_TRANSFER_MASK; +} + +static bool isHdrDataspace(ui::Dataspace dataspace) { + const auto transfer = extractTransfer(dataspace); + + return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG; +} + +static ui::Dataspace getDataspace(const SkImageInfo& image) { + return static_cast<ui::Dataspace>( + ColorSpaceToADataSpace(image.colorSpace(), image.colorType())); +} + +} // namespace + +// Given a source and destination image info, and the max content luminance, generate a tonemaping +// shader and tag it on the supplied paint. +void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits, + SkPaint& paint) { + const auto sourceDataspace = getDataspace(source); + const auto destinationDataspace = getDataspace(destination); + + if (extractTransfer(sourceDataspace) != extractTransfer(destinationDataspace) && + (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace))) { + const auto effect = shaders::LinearEffect{ + .inputDataspace = sourceDataspace, + .outputDataspace = destinationDataspace, + .undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType, + .fakeInputDataspace = destinationDataspace, + .type = shaders::LinearEffect::SkSLType::ColorFilter}; + constexpr float kMaxDisplayBrightnessNits = 1000.f; + constexpr float kCurrentDisplayBrightnessNits = 500.f; + sk_sp<SkColorFilter> colorFilter = createLinearEffectColorFilter( + effect, kMaxDisplayBrightnessNits, kCurrentDisplayBrightnessNits, maxLuminanceNits); + + if (paint.getColorFilter()) { + paint.setColorFilter(SkColorFilters::Compose(paint.refColorFilter(), colorFilter)); + } else { + paint.setColorFilter(colorFilter); + } + } +} + +} // namespace android::uirenderer diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java b/libs/hwui/Tonemapper.h index 9c82eea1e8b8..c0d5325fa9f8 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java +++ b/libs/hwui/Tonemapper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright 2022 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. @@ -14,16 +14,15 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.testapp; +#pragma once -import android.app.Activity; -import android.os.Bundle; +#include <SkCanvas.h> -public class SplitScreenActivity extends Activity { +namespace android::uirenderer { - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - setContentView(R.layout.activity_splitscreen); - } -} +// Given a source and destination image info, and the max content luminance, generate a tonemaping +// shader and tag it on the supplied paint. +void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits, + SkPaint& paint); + +} // namespace android::uirenderer diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 983c7766273a..536ff781badc 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -21,9 +21,10 @@ #include <utils/Log.h> #include "PathParser.h" -#include "SkColorFilter.h" +#include "SkImage.h" #include "SkImageInfo.h" -#include "SkShader.h" +#include "SkSamplingOptions.h" +#include "SkScalar.h" #include "hwui/Paint.h" #ifdef __ANDROID__ diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index 30bb04ae8361..c92654c479c1 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -31,6 +31,7 @@ #include <SkPath.h> #include <SkPathMeasure.h> #include <SkRect.h> +#include <SkRefCnt.h> #include <SkShader.h> #include <SkSurface.h> diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp index 942c0506321c..b7a15633ff6d 100644 --- a/libs/hwui/apex/LayoutlibLoader.cpp +++ b/libs/hwui/apex/LayoutlibLoader.cpp @@ -53,6 +53,7 @@ extern int register_android_graphics_FontFamily(JNIEnv* env); extern int register_android_graphics_Matrix(JNIEnv* env); extern int register_android_graphics_Paint(JNIEnv* env); extern int register_android_graphics_Path(JNIEnv* env); +extern int register_android_graphics_PathIterator(JNIEnv* env); extern int register_android_graphics_PathMeasure(JNIEnv* env); extern int register_android_graphics_Picture(JNIEnv* env); extern int register_android_graphics_Region(JNIEnv* env); @@ -100,6 +101,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)}, {"android.graphics.Path", REG_JNI(register_android_graphics_Path)}, {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)}, + {"android.graphics.PathIterator", REG_JNI(register_android_graphics_PathIterator)}, {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)}, {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)}, {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)}, diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp index bc6bc456ba5a..c442a7b1d17c 100644 --- a/libs/hwui/apex/android_bitmap.cpp +++ b/libs/hwui/apex/android_bitmap.cpp @@ -24,6 +24,11 @@ #include <GraphicsJNI.h> #include <hwui/Bitmap.h> +#include <SkBitmap.h> +#include <SkColorSpace.h> +#include <SkImageInfo.h> +#include <SkRefCnt.h> +#include <SkStream.h> #include <utils/Color.h> using namespace android; diff --git a/libs/hwui/apex/android_canvas.cpp b/libs/hwui/apex/android_canvas.cpp index 2a939efed9bb..905b123076a2 100644 --- a/libs/hwui/apex/android_canvas.cpp +++ b/libs/hwui/apex/android_canvas.cpp @@ -23,7 +23,9 @@ #include <utils/Color.h> #include <SkBitmap.h> +#include <SkColorSpace.h> #include <SkSurface.h> +#include <SkRefCnt.h> using namespace android; diff --git a/libs/hwui/apex/android_paint.cpp b/libs/hwui/apex/android_paint.cpp index 70bd085343ce..cc79cba5e19c 100644 --- a/libs/hwui/apex/android_paint.cpp +++ b/libs/hwui/apex/android_paint.cpp @@ -19,6 +19,7 @@ #include "TypeCast.h" #include <hwui/Paint.h> +#include <SkBlendMode.h> using namespace android; diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index e1f5abd786bf..b1aa19475518 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -37,6 +37,7 @@ extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); extern int register_android_graphics_Graphics(JNIEnv* env); extern int register_android_graphics_ImageDecoder(JNIEnv*); extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*); +extern int register_android_graphics_drawable_LottieDrawable(JNIEnv*); extern int register_android_graphics_Interpolator(JNIEnv* env); extern int register_android_graphics_MaskFilter(JNIEnv* env); extern int register_android_graphics_Movie(JNIEnv* env); @@ -59,6 +60,7 @@ extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env); extern int register_android_graphics_Matrix(JNIEnv* env); extern int register_android_graphics_Paint(JNIEnv* env); extern int register_android_graphics_Path(JNIEnv* env); +extern int register_android_graphics_PathIterator(JNIEnv* env); extern int register_android_graphics_PathMeasure(JNIEnv* env); extern int register_android_graphics_Picture(JNIEnv*); extern int register_android_graphics_Region(JNIEnv* env); @@ -75,11 +77,14 @@ extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env); extern int register_android_graphics_text_MeasuredText(JNIEnv* env); extern int register_android_graphics_text_LineBreaker(JNIEnv *env); extern int register_android_graphics_text_TextShaper(JNIEnv *env); +extern int register_android_graphics_MeshSpecification(JNIEnv* env); +extern int register_android_graphics_Mesh(JNIEnv* env); extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); extern int register_android_view_RenderNode(JNIEnv* env); extern int register_android_view_ThreadedRenderer(JNIEnv* env); +extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); #ifdef NDEBUG #define REG_JNI(name) { name } @@ -94,59 +99,65 @@ extern int register_android_view_ThreadedRenderer(JNIEnv* env); }; #endif -static const RegJNIRec gRegJNI[] = { - REG_JNI(register_android_graphics_Canvas), - // This needs to be before register_android_graphics_Graphics, or the latter - // will not be able to find the jmethodID for ColorSpace.get(). - REG_JNI(register_android_graphics_ColorSpace), - REG_JNI(register_android_graphics_Graphics), - REG_JNI(register_android_graphics_Bitmap), - REG_JNI(register_android_graphics_BitmapFactory), - REG_JNI(register_android_graphics_BitmapRegionDecoder), - REG_JNI(register_android_graphics_ByteBufferStreamAdaptor), - REG_JNI(register_android_graphics_Camera), - REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor), - REG_JNI(register_android_graphics_CanvasProperty), - REG_JNI(register_android_graphics_ColorFilter), - REG_JNI(register_android_graphics_DrawFilter), - REG_JNI(register_android_graphics_FontFamily), - REG_JNI(register_android_graphics_HardwareRendererObserver), - REG_JNI(register_android_graphics_ImageDecoder), - REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable), - REG_JNI(register_android_graphics_Interpolator), - REG_JNI(register_android_graphics_MaskFilter), - REG_JNI(register_android_graphics_Matrix), - REG_JNI(register_android_graphics_Movie), - REG_JNI(register_android_graphics_NinePatch), - REG_JNI(register_android_graphics_Paint), - REG_JNI(register_android_graphics_Path), - REG_JNI(register_android_graphics_PathMeasure), - REG_JNI(register_android_graphics_PathEffect), - REG_JNI(register_android_graphics_Picture), - REG_JNI(register_android_graphics_Region), - REG_JNI(register_android_graphics_Shader), - REG_JNI(register_android_graphics_RenderEffect), - REG_JNI(register_android_graphics_TextureLayer), - REG_JNI(register_android_graphics_Typeface), - REG_JNI(register_android_graphics_YuvImage), - REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory), - REG_JNI(register_android_graphics_animation_RenderNodeAnimator), - REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable), - REG_JNI(register_android_graphics_drawable_VectorDrawable), - REG_JNI(register_android_graphics_fonts_Font), - REG_JNI(register_android_graphics_fonts_FontFamily), - REG_JNI(register_android_graphics_pdf_PdfDocument), - REG_JNI(register_android_graphics_pdf_PdfEditor), - REG_JNI(register_android_graphics_pdf_PdfRenderer), - REG_JNI(register_android_graphics_text_MeasuredText), - REG_JNI(register_android_graphics_text_LineBreaker), - REG_JNI(register_android_graphics_text_TextShaper), - - REG_JNI(register_android_util_PathParser), - REG_JNI(register_android_view_RenderNode), - REG_JNI(register_android_view_DisplayListCanvas), - REG_JNI(register_android_view_ThreadedRenderer), -}; + static const RegJNIRec gRegJNI[] = { + REG_JNI(register_android_graphics_Canvas), + // This needs to be before register_android_graphics_Graphics, or the latter + // will not be able to find the jmethodID for ColorSpace.get(). + REG_JNI(register_android_graphics_ColorSpace), + REG_JNI(register_android_graphics_Graphics), + REG_JNI(register_android_graphics_Bitmap), + REG_JNI(register_android_graphics_BitmapFactory), + REG_JNI(register_android_graphics_BitmapRegionDecoder), + REG_JNI(register_android_graphics_ByteBufferStreamAdaptor), + REG_JNI(register_android_graphics_Camera), + REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor), + REG_JNI(register_android_graphics_CanvasProperty), + REG_JNI(register_android_graphics_ColorFilter), + REG_JNI(register_android_graphics_DrawFilter), + REG_JNI(register_android_graphics_FontFamily), + REG_JNI(register_android_graphics_HardwareRendererObserver), + REG_JNI(register_android_graphics_ImageDecoder), + REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable), + REG_JNI(register_android_graphics_drawable_LottieDrawable), + REG_JNI(register_android_graphics_Interpolator), + REG_JNI(register_android_graphics_MaskFilter), + REG_JNI(register_android_graphics_Matrix), + REG_JNI(register_android_graphics_Movie), + REG_JNI(register_android_graphics_NinePatch), + REG_JNI(register_android_graphics_Paint), + REG_JNI(register_android_graphics_Path), + REG_JNI(register_android_graphics_PathIterator), + REG_JNI(register_android_graphics_PathMeasure), + REG_JNI(register_android_graphics_PathEffect), + REG_JNI(register_android_graphics_Picture), + REG_JNI(register_android_graphics_Region), + REG_JNI(register_android_graphics_Shader), + REG_JNI(register_android_graphics_RenderEffect), + REG_JNI(register_android_graphics_TextureLayer), + REG_JNI(register_android_graphics_Typeface), + REG_JNI(register_android_graphics_YuvImage), + REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory), + REG_JNI(register_android_graphics_animation_RenderNodeAnimator), + REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable), + REG_JNI(register_android_graphics_drawable_VectorDrawable), + REG_JNI(register_android_graphics_fonts_Font), + REG_JNI(register_android_graphics_fonts_FontFamily), + REG_JNI(register_android_graphics_pdf_PdfDocument), + REG_JNI(register_android_graphics_pdf_PdfEditor), + REG_JNI(register_android_graphics_pdf_PdfRenderer), + REG_JNI(register_android_graphics_text_MeasuredText), + REG_JNI(register_android_graphics_text_LineBreaker), + REG_JNI(register_android_graphics_text_TextShaper), + REG_JNI(register_android_graphics_MeshSpecification), + REG_JNI(register_android_graphics_Mesh), + + REG_JNI(register_android_util_PathParser), + REG_JNI(register_android_view_RenderNode), + REG_JNI(register_android_view_DisplayListCanvas), + REG_JNI(register_android_graphics_HardwareBufferRenderer), + + REG_JNI(register_android_view_ThreadedRenderer), + }; } // namespace android diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h index fdc97a4fd8ba..2dcbca8273e7 100644 --- a/libs/hwui/canvas/CanvasOps.h +++ b/libs/hwui/canvas/CanvasOps.h @@ -17,13 +17,19 @@ #pragma once #include <SkAndroidFrameworkUtils.h> +#include <SkBlendMode.h> #include <SkCanvas.h> -#include <SkPath.h> -#include <SkRegion.h> -#include <SkVertices.h> +#include <SkClipOp.h> #include <SkImage.h> +#include <SkPaint.h> +#include <SkPath.h> #include <SkPicture.h> +#include <SkRRect.h> +#include <SkRect.h> +#include <SkRegion.h> #include <SkRuntimeEffect.h> +#include <SkSamplingOptions.h> +#include <SkVertices.h> #include <log/log.h> diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 67f47580a70f..feafc2372442 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -35,9 +35,15 @@ #endif #include <SkCanvas.h> +#include <SkColor.h> +#include <SkEncodedImageFormat.h> +#include <SkHighContrastFilter.h> +#include <SkImageEncoder.h> #include <SkImagePriv.h> +#include <SkPixmap.h> +#include <SkRect.h> +#include <SkStream.h> #include <SkWebpEncoder.h> -#include <SkHighContrastFilter.h> #include <limits> namespace android { diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 94a047c06ced..133f1fe0a1e7 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -19,9 +19,9 @@ #include <SkColorFilter.h> #include <SkColorSpace.h> #include <SkImage.h> -#include <SkImage.h> #include <SkImageInfo.h> #include <SkPixelRef.h> +#include <SkRefCnt.h> #include <cutils/compiler.h> #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration #include <android/hardware_buffer.h> diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp index 270d24af99fd..8b52551fc107 100644 --- a/libs/hwui/hwui/BlurDrawLooper.cpp +++ b/libs/hwui/hwui/BlurDrawLooper.cpp @@ -15,6 +15,8 @@ */ #include "BlurDrawLooper.h" +#include <SkBlurTypes.h> +#include <SkColorSpace.h> #include <SkMaskFilter.h> namespace android { diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index b046f45d9c57..cd8af3d933b1 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -26,6 +26,7 @@ #include "hwui/PaintFilter.h" #include <SkFontMetrics.h> +#include <SkRRect.h> namespace android { diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 82777646f3a2..07e2fe24c939 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -30,7 +30,9 @@ #include <SkMatrix.h> class SkAnimatedImage; +enum class SkBlendMode; class SkCanvasState; +class SkRRect; class SkRuntimeShaderBuilder; class SkVertices; @@ -58,6 +60,7 @@ typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; typedef std::function<void(uint16_t* text, float* positions)> ReadGlyphFunc; class AnimatedImageDrawable; +class LottieDrawable; class Bitmap; class Paint; struct Typeface; @@ -151,7 +154,7 @@ public: LOG_ALWAYS_FATAL("Not supported"); } - virtual void punchHole(const SkRRect& rect) = 0; + virtual void punchHole(const SkRRect& rect, float alpha) = 0; // ---------------------------------------------------------------------------- // Canvas state operations @@ -225,6 +228,7 @@ public: float sweepAngle, bool useCenter, const Paint& paint) = 0; virtual void drawPath(const SkPath& path, const Paint& paint) = 0; virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) = 0; + virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender>, const SkPaint& paint) = 0; // Bitmap-based virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) = 0; @@ -239,6 +243,7 @@ public: const Paint* paint) = 0; virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) = 0; + virtual void drawLottie(LottieDrawable* lottieDrawable) = 0; virtual void drawPicture(const SkPicture& picture) = 0; /** diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h index cef2233fc371..b6d73b39d8d0 100644 --- a/libs/hwui/hwui/ImageDecoder.h +++ b/libs/hwui/hwui/ImageDecoder.h @@ -17,9 +17,11 @@ #include <SkAndroidCodec.h> #include <SkCodec.h> +#include <SkColorSpace.h> #include <SkImageInfo.h> #include <SkPngChunkReader.h> #include <SkRect.h> +#include <SkRefCnt.h> #include <SkSize.h> #include <cutils/compiler.h> diff --git a/libs/hwui/hwui/LottieDrawable.cpp b/libs/hwui/hwui/LottieDrawable.cpp new file mode 100644 index 000000000000..92dc51e01a85 --- /dev/null +++ b/libs/hwui/hwui/LottieDrawable.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "LottieDrawable.h" + +#include <SkTime.h> +#include <log/log.h> +#include <pipeline/skia/SkiaUtils.h> + +namespace android { + +sk_sp<LottieDrawable> LottieDrawable::Make(sk_sp<skottie::Animation> animation, size_t bytesUsed) { + if (animation) { + return sk_sp<LottieDrawable>(new LottieDrawable(std::move(animation), bytesUsed)); + } + return nullptr; +} +LottieDrawable::LottieDrawable(sk_sp<skottie::Animation> animation, size_t bytesUsed) + : mAnimation(std::move(animation)), mBytesUsed(bytesUsed) {} + +bool LottieDrawable::start() { + if (mRunning) { + return false; + } + + mRunning = true; + return true; +} + +bool LottieDrawable::stop() { + bool wasRunning = mRunning; + mRunning = false; + return wasRunning; +} + +bool LottieDrawable::isRunning() { + return mRunning; +} + +// TODO: Check to see if drawable is actually dirty +bool LottieDrawable::isDirty() { + return true; +} + +void LottieDrawable::onDraw(SkCanvas* canvas) { + if (mRunning) { + const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + + nsecs_t t = 0; + if (mStartTime == 0) { + mStartTime = currentTime; + } else { + t = currentTime - mStartTime; + } + double seekTime = std::fmod((double)t * 1e-9, mAnimation->duration()); + mAnimation->seekFrameTime(seekTime); + mAnimation->render(canvas); + } +} + +void LottieDrawable::drawStaging(SkCanvas* canvas) { + onDraw(canvas); +} + +SkRect LottieDrawable::onGetBounds() { + // We do not actually know the bounds, so give a conservative answer. + return SkRectMakeLargest(); +} + +} // namespace android diff --git a/libs/hwui/hwui/LottieDrawable.h b/libs/hwui/hwui/LottieDrawable.h new file mode 100644 index 000000000000..9cc34bf12f4f --- /dev/null +++ b/libs/hwui/hwui/LottieDrawable.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 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 <SkDrawable.h> +#include <Skottie.h> +#include <utils/Timers.h> + +class SkCanvas; + +namespace android { + +/** + * Native component of android.graphics.drawable.LottieDrawable.java. + * This class can be drawn into Canvas.h and maintains the state needed to drive + * the animation from the RenderThread. + */ +class LottieDrawable : public SkDrawable { +public: + static sk_sp<LottieDrawable> Make(sk_sp<skottie::Animation> animation, size_t bytes); + + // Draw to software canvas + void drawStaging(SkCanvas* canvas); + + // Returns true if the animation was started; false otherwise (e.g. it was + // already running) + bool start(); + // Returns true if the animation was stopped; false otherwise (e.g. it was + // already stopped) + bool stop(); + bool isRunning(); + + // TODO: Is dirty should take in a time til next frame to determine if it is dirty + bool isDirty(); + + SkRect onGetBounds() override; + + size_t byteSize() const { return sizeof(*this) + mBytesUsed; } + +protected: + void onDraw(SkCanvas* canvas) override; + +private: + LottieDrawable(sk_sp<skottie::Animation> animation, size_t bytes_used); + + sk_sp<skottie::Animation> mAnimation; + bool mRunning = false; + // The start time for the drawable itself. + nsecs_t mStartTime = 0; + const size_t mBytesUsed = 0; +}; + +} // namespace android diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index 2db3ace1cd43..34cb4aef70d9 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -16,10 +16,13 @@ #include "MinikinSkia.h" -#include <SkFontDescriptor.h> #include <SkFont.h> +#include <SkFontDescriptor.h> #include <SkFontMetrics.h> #include <SkFontMgr.h> +#include <SkRect.h> +#include <SkScalar.h> +#include <SkStream.h> #include <SkTypeface.h> #include <log/log.h> diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index 5a9d2508230e..3c67edc9a428 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -125,9 +125,14 @@ Typeface* Typeface::createWithDifferentBaseWeight(Typeface* src, int weight) { } Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families, - int weight, int italic) { + int weight, int italic, const Typeface* fallback) { Typeface* result = new Typeface; - result->fFontCollection.reset(new minikin::FontCollection(families)); + if (fallback == nullptr) { + result->fFontCollection = minikin::FontCollection::create(std::move(families)); + } else { + result->fFontCollection = + fallback->fFontCollection->createCollectionWithFamilies(std::move(families)); + } if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) { int weightFromFont; @@ -191,8 +196,8 @@ void Typeface::setRobotoTypefaceForTest() { std::vector<std::shared_ptr<minikin::Font>> fonts; fonts.push_back(minikin::Font::Builder(font).build()); - std::shared_ptr<minikin::FontCollection> collection = std::make_shared<minikin::FontCollection>( - std::make_shared<minikin::FontFamily>(std::move(fonts))); + std::shared_ptr<minikin::FontCollection> collection = + minikin::FontCollection::create(minikin::FontFamily::create(std::move(fonts))); Typeface* hwTypeface = new Typeface(); hwTypeface->fFontCollection = collection; diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h index 0c3ef01ab26b..565136e53676 100644 --- a/libs/hwui/hwui/Typeface.h +++ b/libs/hwui/hwui/Typeface.h @@ -78,7 +78,8 @@ public: Typeface* src, const std::vector<minikin::FontVariation>& variations); static Typeface* createFromFamilies( - std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic); + std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic, + const Typeface* fallback); static void setDefault(const Typeface* face); diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp index c40b858268be..373e893b9a25 100644 --- a/libs/hwui/jni/AnimatedImageDrawable.cpp +++ b/libs/hwui/jni/AnimatedImageDrawable.cpp @@ -21,8 +21,11 @@ #include <SkAndroidCodec.h> #include <SkAnimatedImage.h> #include <SkColorFilter.h> +#include <SkEncodedImageFormat.h> #include <SkPicture.h> #include <SkPictureRecorder.h> +#include <SkRect.h> +#include <SkRefCnt.h> #include <hwui/AnimatedImageDrawable.h> #include <hwui/ImageDecoder.h> #include <hwui/Canvas.h> diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 5db0783cf83e..540abec38ebc 100755 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -2,40 +2,41 @@ #define LOG_TAG "Bitmap" #include "Bitmap.h" +#include <hwui/Bitmap.h> +#include <hwui/Paint.h> + +#include "CreateJavaOutputStreamAdaptor.h" +#include "GraphicsJNI.h" +#include "HardwareBufferHelpers.h" #include "SkBitmap.h" +#include "SkBlendMode.h" #include "SkCanvas.h" #include "SkColor.h" #include "SkColorSpace.h" -#include "SkPixelRef.h" -#include "SkImageEncoder.h" +#include "SkData.h" #include "SkImageInfo.h" -#include "GraphicsJNI.h" +#include "SkPaint.h" +#include "SkPixmap.h" +#include "SkPoint.h" +#include "SkRefCnt.h" #include "SkStream.h" -#include "SkWebpEncoder.h" - +#include "SkTypes.h" #include "android_nio_utils.h" -#include "CreateJavaOutputStreamAdaptor.h" -#include <hwui/Paint.h> -#include <hwui/Bitmap.h> -#include <utils/Color.h> #ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread #include <android-base/unique_fd.h> #include <android/binder_parcel.h> #include <android/binder_parcel_jni.h> #include <android/binder_parcel_platform.h> -#include <android/binder_parcel_utils.h> -#include <private/android/AHardwareBufferHelpers.h> #include <cutils/ashmem.h> -#include <dlfcn.h> #include <renderthread/RenderProxy.h> #include <sys/mman.h> #endif #include <inttypes.h> #include <string.h> + #include <memory> -#include <string> #define DEBUG_PARCEL 0 @@ -1189,18 +1190,11 @@ static jobject Bitmap_copyPreserveInternalConfig(JNIEnv* env, jobject, jlong bit return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false)); } -#ifdef __ANDROID__ // Layoutlib does not support graphic buffer -typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject); -AHB_from_HB AHardwareBuffer_fromHardwareBuffer; - -typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*); -AHB_to_HB AHardwareBuffer_toHardwareBuffer; -#endif - static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer, jlong colorSpacePtr) { #ifdef __ANDROID__ // Layoutlib does not support graphic buffer - AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer); + AHardwareBuffer* buffer = uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer( + env, hardwareBuffer); sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, GraphicsJNI::getNativeColorSpace(colorSpacePtr)); if (!bitmap.get()) { @@ -1223,7 +1217,8 @@ static jobject Bitmap_getHardwareBuffer(JNIEnv* env, jobject, jlong bitmapPtr) { } Bitmap& bitmap = bitmapHandle->bitmap(); - return AHardwareBuffer_toHardwareBuffer(env, bitmap.hardwareBuffer()); + return uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer( + env, bitmap.hardwareBuffer()); #else return nullptr; #endif @@ -1321,18 +1316,7 @@ int register_android_graphics_Bitmap(JNIEnv* env) gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J"); gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V"); gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V"); - -#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel - void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); - AHardwareBuffer_fromHardwareBuffer = - (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer"); - LOG_ALWAYS_FATAL_IF(AHardwareBuffer_fromHardwareBuffer == nullptr, - "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!"); - - AHardwareBuffer_toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer"); - LOG_ALWAYS_FATAL_IF(AHardwareBuffer_toHardwareBuffer == nullptr, - " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!"); -#endif + uirenderer::HardwareBufferHelpers::init(); return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods, NELEM(gBitmapMethods)); } diff --git a/libs/hwui/jni/Bitmap.h b/libs/hwui/jni/Bitmap.h index 73eca3aa8ef8..21a93f066d9b 100644 --- a/libs/hwui/jni/Bitmap.h +++ b/libs/hwui/jni/Bitmap.h @@ -19,7 +19,6 @@ #include <jni.h> #include <android/bitmap.h> -class SkBitmap; struct SkImageInfo; namespace android { diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp index 4e9daa4b0c16..320d3322904f 100644 --- a/libs/hwui/jni/BitmapFactory.cpp +++ b/libs/hwui/jni/BitmapFactory.cpp @@ -8,9 +8,19 @@ #include "MimeType.h" #include "NinePatchPeeker.h" #include "SkAndroidCodec.h" +#include "SkBitmap.h" +#include "SkBlendMode.h" #include "SkCanvas.h" +#include "SkColorSpace.h" +#include "SkEncodedImageFormat.h" +#include "SkImageInfo.h" #include "SkMath.h" +#include "SkPaint.h" #include "SkPixelRef.h" +#include "SkRect.h" +#include "SkRefCnt.h" +#include "SkSamplingOptions.h" +#include "SkSize.h" #include "SkStream.h" #include "SkString.h" #include "SkUtils.h" diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp index 1c20415dcc8f..eb56ae310231 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -25,6 +25,7 @@ #include "BitmapRegionDecoder.h" #include "SkBitmap.h" #include "SkCodec.h" +#include "SkColorSpace.h" #include "SkData.h" #include "SkStream.h" diff --git a/libs/hwui/jni/ByteBufferStreamAdaptor.cpp b/libs/hwui/jni/ByteBufferStreamAdaptor.cpp index b10540cb3fbd..97dbc9ac171f 100644 --- a/libs/hwui/jni/ByteBufferStreamAdaptor.cpp +++ b/libs/hwui/jni/ByteBufferStreamAdaptor.cpp @@ -2,6 +2,7 @@ #include "GraphicsJNI.h" #include "Utils.h" +#include <SkData.h> #include <SkStream.h> using namespace android; diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp index cef21f91f3c1..4bd7ef47b871 100644 --- a/libs/hwui/jni/ColorFilter.cpp +++ b/libs/hwui/jni/ColorFilter.cpp @@ -17,6 +17,7 @@ #include "GraphicsJNI.h" +#include "SkBlendMode.h" #include "SkColorFilter.h" #include "SkColorMatrixFilter.h" diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp index ce5ac382aeff..28e71d74e5b9 100644 --- a/libs/hwui/jni/FontFamily.cpp +++ b/libs/hwui/jni/FontFamily.cpp @@ -24,6 +24,7 @@ #include "SkData.h" #include "SkFontMgr.h" #include "SkRefCnt.h" +#include "SkStream.h" #include "SkTypeface.h" #include "Utils.h" #include "fonts/Font.h" @@ -84,9 +85,9 @@ static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) { if (builder->fonts.empty()) { return 0; } - std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>( + std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create( builder->langId, builder->variant, std::move(builder->fonts), - true /* isCustomFallback */); + true /* isCustomFallback */, false /* isDefaultFallback */); if (family->getCoverage().length() == 0) { return 0; } diff --git a/libs/hwui/jni/GIFMovie.cpp b/libs/hwui/jni/GIFMovie.cpp index fef51b8d2f79..ae6ac4ce4ecc 100644 --- a/libs/hwui/jni/GIFMovie.cpp +++ b/libs/hwui/jni/GIFMovie.cpp @@ -7,9 +7,11 @@ #include "Movie.h" +#include "SkBitmap.h" #include "SkColor.h" #include "SkColorPriv.h" #include "SkStream.h" +#include "SkTypes.h" #include "gif_lib.h" diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp index 33669ac0a34e..c8358497ad62 100644 --- a/libs/hwui/jni/Graphics.cpp +++ b/libs/hwui/jni/Graphics.cpp @@ -8,10 +8,18 @@ #include <nativehelper/JNIHelp.h> #include "GraphicsJNI.h" +#include "include/private/SkTemplates.h" // SkTAddOffset +#include "SkBitmap.h" #include "SkCanvas.h" +#include "SkColorSpace.h" #include "SkFontMetrics.h" +#include "SkImageInfo.h" #include "SkMath.h" +#include "SkPixelRef.h" +#include "SkPoint.h" +#include "SkRect.h" #include "SkRegion.h" +#include "SkTypes.h" #include <cutils/ashmem.h> #include <hwui/Canvas.h> @@ -568,14 +576,22 @@ jobject GraphicsJNI::getColorSpace(JNIEnv* env, SkColorSpace* decodeColorSpace, LOG_ALWAYS_FATAL_IF(!decodeColorSpace->toXYZD50(&xyzMatrix)); skcms_TransferFunction transferParams; - // We can only handle numerical transfer functions at the moment - LOG_ALWAYS_FATAL_IF(!decodeColorSpace->isNumericalTransferFn(&transferParams)); - - jobject params = env->NewObject(gTransferParameters_class, - gTransferParameters_constructorMethodID, - transferParams.a, transferParams.b, transferParams.c, - transferParams.d, transferParams.e, transferParams.f, - transferParams.g); + decodeColorSpace->transferFn(&transferParams); + auto res = skcms_TransferFunction_getType(&transferParams); + LOG_ALWAYS_FATAL_IF(res == skcms_TFType_HLGinvish || res == skcms_TFType_Invalid); + + jobject params; + if (res == skcms_TFType_PQish || res == skcms_TFType_HLGish) { + params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID, + transferParams.a, transferParams.b, transferParams.c, + transferParams.d, transferParams.e, transferParams.f, + transferParams.g, true); + } else { + params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID, + transferParams.a, transferParams.b, transferParams.c, + transferParams.d, transferParams.e, transferParams.f, + transferParams.g, false); + } jfloatArray xyzArray = env->NewFloatArray(9); jfloat xyz[9] = { @@ -800,8 +816,8 @@ int register_android_graphics_Graphics(JNIEnv* env) gTransferParameters_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ColorSpace$Rgb$TransferParameters")); - gTransferParameters_constructorMethodID = GetMethodIDOrDie(env, gTransferParameters_class, - "<init>", "(DDDDDDD)V"); + gTransferParameters_constructorMethodID = + GetMethodIDOrDie(env, gTransferParameters_class, "<init>", "(DDDDDDDZ)V"); gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics"); gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class); diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h index 085a905abaf8..c4a61ccd1e5f 100644 --- a/libs/hwui/jni/GraphicsJNI.h +++ b/libs/hwui/jni/GraphicsJNI.h @@ -2,19 +2,18 @@ #define _ANDROID_GRAPHICS_GRAPHICS_JNI_H_ #include <cutils/compiler.h> +#include <hwui/Bitmap.h> +#include <hwui/Canvas.h> -#include "Bitmap.h" #include "BRDAllocator.h" +#include "Bitmap.h" #include "SkBitmap.h" #include "SkCodec.h" -#include "SkPixelRef.h" +#include "SkColorSpace.h" #include "SkMallocPixelRef.h" +#include "SkPixelRef.h" #include "SkPoint.h" #include "SkRect.h" -#include "SkColorSpace.h" -#include <hwui/Canvas.h> -#include <hwui/Bitmap.h> - #include "graphics_jni_helpers.h" class SkCanvas; @@ -335,6 +334,26 @@ private: int fLen; }; +class JGlobalRefHolder { +public: + JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {} + + virtual ~JGlobalRefHolder() { + GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject); + mObject = nullptr; + } + + jobject object() { return mObject; } + JavaVM* vm() { return mVm; } + +private: + JGlobalRefHolder(const JGlobalRefHolder&) = delete; + void operator=(const JGlobalRefHolder&) = delete; + + JavaVM* mVm; + jobject mObject; +}; + void doThrowNPE(JNIEnv* env); void doThrowAIOOBE(JNIEnv* env); // Array Index Out Of Bounds Exception void doThrowIAE(JNIEnv* env, const char* msg = NULL); // Illegal Argument diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp new file mode 100644 index 000000000000..7e3f771b6b3d --- /dev/null +++ b/libs/hwui/jni/HardwareBufferHelpers.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "HardwareBufferHelpers.h" + +#include <dlfcn.h> +#include <log/log.h> + +#ifdef __ANDROID__ +typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject); +typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*); +static AHB_from_HB fromHardwareBuffer = nullptr; +static AHB_to_HB toHardwareBuffer = nullptr; +#endif + +void android::uirenderer::HardwareBufferHelpers::init() { +#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel + void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); + fromHardwareBuffer = (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer"); + LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr, + "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!"); + + toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer"); + LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr, + " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!"); +#endif +} + +AHardwareBuffer* android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer( + JNIEnv* env, jobject hardwarebuffer) { +#ifdef __ANDROID__ + LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr, + "Failed to find symbol AHardwareBuffer_fromHardwareBuffer, did you forget " + "to call HardwareBufferHelpers::init?"); + return fromHardwareBuffer(env, hardwarebuffer); +#else + ALOGE("ERROR attempting to invoke AHardwareBuffer_fromHardwareBuffer on non Android " + "configuration"); + return nullptr; +#endif +} + +jobject android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer( + JNIEnv* env, AHardwareBuffer* ahardwarebuffer) { +#ifdef __ANDROID__ + LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr, + "Failed to find symbol AHardwareBuffer_toHardwareBuffer, did you forget to " + "call HardwareBufferHelpers::init?"); + return toHardwareBuffer(env, ahardwarebuffer); +#else + ALOGE("ERROR attempting to invoke AHardwareBuffer_toHardwareBuffer on non Android " + "configuration"); + return nullptr; +#endif +}
\ No newline at end of file diff --git a/libs/hwui/jni/HardwareBufferHelpers.h b/libs/hwui/jni/HardwareBufferHelpers.h new file mode 100644 index 000000000000..326babfb0b34 --- /dev/null +++ b/libs/hwui/jni/HardwareBufferHelpers.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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. + */ +#ifndef HARDWAREBUFFER_JNI_HELPERS_H +#define HARDWAREBUFFER_JNI_HELPERS_H + +#include <android/bitmap.h> +#include <jni.h> + +namespace android { +namespace uirenderer { + +class HardwareBufferHelpers { +public: + static void init(); + static AHardwareBuffer* AHardwareBuffer_fromHardwareBuffer(JNIEnv*, jobject); + static jobject AHardwareBuffer_toHardwareBuffer(JNIEnv*, AHardwareBuffer*); + +private: + HardwareBufferHelpers() = default; // not to be instantiated +}; + +} // namespace uirenderer +} // namespace android + +#endif // HARDWAREBUFFER_JNI_HELPERS_H
\ No newline at end of file diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index f7b8c014be6e..bad710dec274 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -29,8 +29,12 @@ #include <FrontBufferedStream.h> #include <SkAndroidCodec.h> -#include <SkEncodedImageFormat.h> +#include <SkBitmap.h> +#include <SkColorSpace.h> +#include <SkImageInfo.h> +#include <SkRect.h> #include <SkStream.h> +#include <SkString.h> #include <androidfw/Asset.h> #include <fcntl.h> diff --git a/libs/hwui/jni/JvmErrorReporter.h b/libs/hwui/jni/JvmErrorReporter.h new file mode 100644 index 000000000000..5e10b9d93275 --- /dev/null +++ b/libs/hwui/jni/JvmErrorReporter.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 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. + */ +#ifndef JVMERRORREPORTER_H +#define JVMERRORREPORTER_H + +#include <TreeInfo.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> + +#include "GraphicsJNI.h" + +namespace android { +namespace uirenderer { + +class JvmErrorReporter : public android::uirenderer::ErrorHandler { +public: + JvmErrorReporter(JNIEnv* env) { env->GetJavaVM(&mVm); } + + virtual void onError(const std::string& message) override { + JNIEnv* env = GraphicsJNI::getJNIEnv(); + jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); + } + +private: + JavaVM* mVm; +}; + +} // namespace uirenderer +} // namespace android + +#endif // JVMERRORREPORTER_H diff --git a/libs/hwui/jni/LottieDrawable.cpp b/libs/hwui/jni/LottieDrawable.cpp new file mode 100644 index 000000000000..fb6eede213a8 --- /dev/null +++ b/libs/hwui/jni/LottieDrawable.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022 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. + */ + +#include <SkRect.h> +#include <Skottie.h> +#include <hwui/Canvas.h> +#include <hwui/LottieDrawable.h> + +#include "GraphicsJNI.h" +#include "Utils.h" + +using namespace android; + +static jclass gLottieDrawableClass; + +static jlong LottieDrawable_nCreate(JNIEnv* env, jobject, jstring jjson) { + const ScopedUtfChars cstr(env, jjson); + // TODO(b/259267150) provide more accurate byteSize + size_t bytes = strlen(cstr.c_str()); + auto animation = skottie::Animation::Builder().make(cstr.c_str(), bytes); + sk_sp<LottieDrawable> drawable(LottieDrawable::Make(std::move(animation), bytes)); + if (!drawable) { + return 0; + } + return reinterpret_cast<jlong>(drawable.release()); +} + +static void LottieDrawable_destruct(LottieDrawable* drawable) { + SkSafeUnref(drawable); +} + +static jlong LottieDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&LottieDrawable_destruct)); +} + +static void LottieDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong canvasPtr) { + auto* drawable = reinterpret_cast<LottieDrawable*>(nativePtr); + auto* canvas = reinterpret_cast<Canvas*>(canvasPtr); + canvas->drawLottie(drawable); +} + +static jboolean LottieDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<LottieDrawable*>(nativePtr); + return drawable->isRunning(); +} + +static jboolean LottieDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<LottieDrawable*>(nativePtr); + return drawable->start(); +} + +static jboolean LottieDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<LottieDrawable*>(nativePtr); + return drawable->stop(); +} + +static jlong LottieDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<LottieDrawable*>(nativePtr); + return drawable->byteSize(); +} + +static const JNINativeMethod gLottieDrawableMethods[] = { + {"nCreate", "(Ljava/lang/String;)J", (void*)LottieDrawable_nCreate}, + {"nNativeByteSize", "(J)J", (void*)LottieDrawable_nNativeByteSize}, + {"nGetNativeFinalizer", "()J", (void*)LottieDrawable_nGetNativeFinalizer}, + {"nDraw", "(JJ)V", (void*)LottieDrawable_nDraw}, + {"nIsRunning", "(J)Z", (void*)LottieDrawable_nIsRunning}, + {"nStart", "(J)Z", (void*)LottieDrawable_nStart}, + {"nStop", "(J)Z", (void*)LottieDrawable_nStop}, +}; + +int register_android_graphics_drawable_LottieDrawable(JNIEnv* env) { + gLottieDrawableClass = reinterpret_cast<jclass>( + env->NewGlobalRef(FindClassOrDie(env, "android/graphics/drawable/LottieDrawable"))); + + return android::RegisterMethodsOrDie(env, "android/graphics/drawable/LottieDrawable", + gLottieDrawableMethods, NELEM(gLottieDrawableMethods)); +} diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp index 5383032e0f77..048ce025ce27 100644 --- a/libs/hwui/jni/MaskFilter.cpp +++ b/libs/hwui/jni/MaskFilter.cpp @@ -2,6 +2,7 @@ #include "SkMaskFilter.h" #include "SkBlurMask.h" #include "SkBlurMaskFilter.h" +#include "SkBlurTypes.h" #include "SkTableMaskFilter.h" static void ThrowIAE_IfNull(JNIEnv* env, void* ptr) { diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp new file mode 100644 index 000000000000..3aac48dd08b1 --- /dev/null +++ b/libs/hwui/jni/Mesh.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2022 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. + */ + +#include <GLES/gl.h> +#include <Mesh.h> +#include <SkMesh.h> + +#include "GraphicsJNI.h" +#include "graphics_jni_helpers.h" + +namespace android { + +sk_sp<SkMesh::VertexBuffer> genVertexBuffer(JNIEnv* env, jobject buffer, int size, + jboolean isDirect) { + auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect); + auto vertexBuffer = SkMesh::MakeVertexBuffer(nullptr, buff.data(), size); + return vertexBuffer; +} + +sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size, + jboolean isDirect) { + auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect); + auto indexBuffer = SkMesh::MakeIndexBuffer(nullptr, buff.data(), size); + return indexBuffer; +} + +static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer, + jboolean isDirect, jint vertexCount, jint vertexOffset, jint left, jint top, + jint right, jint bottom) { + auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec)); + sk_sp<SkMesh::VertexBuffer> skVertexBuffer = + genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect); + auto skRect = SkRect::MakeLTRB(left, top, right, bottom); + auto meshResult = SkMesh::Make( + skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset, + SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect); + + if (!meshResult.error.isEmpty()) { + jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str()); + } + + auto meshPtr = std::make_unique<MeshWrapper>( + MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)}); + return reinterpret_cast<jlong>(meshPtr.release()); +} + +static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer, + jboolean isVertexDirect, jint vertexCount, jint vertexOffset, + jobject indexBuffer, jboolean isIndexDirect, jint indexCount, + jint indexOffset, jint left, jint top, jint right, jint bottom) { + auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec)); + sk_sp<SkMesh::VertexBuffer> skVertexBuffer = + genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isVertexDirect); + sk_sp<SkMesh::IndexBuffer> skIndexBuffer = + genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect); + auto skRect = SkRect::MakeLTRB(left, top, right, bottom); + + auto meshResult = SkMesh::MakeIndexed( + skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset, + skIndexBuffer, indexCount, indexOffset, + SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect); + + if (!meshResult.error.isEmpty()) { + jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str()); + } + auto meshPtr = std::make_unique<MeshWrapper>( + MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)}); + return reinterpret_cast<jlong>(meshPtr.release()); +} + +static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed) { + auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); + auto mesh = wrapper->mesh; + if (indexed) { + wrapper->mesh = SkMesh::MakeIndexed(sk_ref_sp(mesh.spec()), mesh.mode(), + sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(), + mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()), + mesh.indexCount(), mesh.indexOffset(), + wrapper->builder.fUniforms, mesh.bounds()) + .mesh; + } else { + wrapper->mesh = SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(), + sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(), + mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds()) + .mesh; + } +} + +static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args); + va_end(args); + return ret; +} + +static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) { + switch (type) { + case SkRuntimeEffect::Uniform::Type::kFloat: + case SkRuntimeEffect::Uniform::Type::kFloat2: + case SkRuntimeEffect::Uniform::Type::kFloat3: + case SkRuntimeEffect::Uniform::Type::kFloat4: + case SkRuntimeEffect::Uniform::Type::kFloat2x2: + case SkRuntimeEffect::Uniform::Type::kFloat3x3: + case SkRuntimeEffect::Uniform::Type::kFloat4x4: + return false; + case SkRuntimeEffect::Uniform::Type::kInt: + case SkRuntimeEffect::Uniform::Type::kInt2: + case SkRuntimeEffect::Uniform::Type::kInt3: + case SkRuntimeEffect::Uniform::Type::kInt4: + return true; + } +} + +static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder, + const char* uniformName, const float values[], int count, + bool isColor) { + MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName); + if (uniform.fVar == nullptr) { + ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); + } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) { + if (isColor) { + jniThrowExceptionFmt( + env, "java/lang/IllegalArgumentException", + "attempting to set a color uniform using the non-color specific APIs: %s %x", + uniformName, uniform.fVar->flags); + } else { + ThrowIAEFmt(env, + "attempting to set a non-color uniform using the setColorUniform APIs: %s", + uniformName); + } + } else if (isIntUniformType(uniform.fVar->type)) { + ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s", + uniformName); + } else if (!uniform.set<float>(values, count)) { + ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", + uniform.fVar->sizeInBytes(), sizeof(float) * count); + } +} + +static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, + jfloat value1, jfloat value2, jfloat value3, jfloat value4, + jint count) { + auto* wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); + ScopedUtfChars name(env, uniformName); + const float values[4] = {value1, value2, value3, value4}; + nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false); +} + +static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName, + jfloatArray jvalues, jboolean isColor) { + auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); + ScopedUtfChars name(env, jUniformName); + AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess); + nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(), + autoValues.length(), isColor); +} + +static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder, + const char* uniformName, const int values[], int count) { + MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName); + if (uniform.fVar == nullptr) { + ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); + } else if (!isIntUniformType(uniform.fVar->type)) { + ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s", + uniformName); + } else if (!uniform.set<int>(values, count)) { + ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", + uniform.fVar->sizeInBytes(), sizeof(float) * count); + } +} + +static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, + jint value1, jint value2, jint value3, jint value4, jint count) { + auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); + ScopedUtfChars name(env, uniformName); + const int values[4] = {value1, value2, value3, value4}; + nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count); +} + +static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, + jintArray values) { + auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); + ScopedUtfChars name(env, uniformName); + AutoJavaIntArray autoValues(env, values, 0); + nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(), + autoValues.length()); +} + +static void MeshWrapper_destroy(MeshWrapper* wrapper) { + delete wrapper; +} + +static jlong getMeshFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy)); +} + +static const JNINativeMethod gMeshMethods[] = { + {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer}, + {"nativeMake", "(JILjava/nio/Buffer;ZIIIIII)J", (void*)make}, + {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIIIII)J", + (void*)makeIndexed}, + {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh}, + {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms}, + {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms}, + {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms}, + {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}}; + +int register_android_graphics_Mesh(JNIEnv* env) { + android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods)); + return 0; +} + +} // namespace android diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h new file mode 100644 index 000000000000..7a73f2d5c470 --- /dev/null +++ b/libs/hwui/jni/Mesh.h @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2022 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. + */ + +#ifndef FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_ +#define FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_ + +#include <SkMesh.h> +#include <jni.h> + +#include <utility> + +#include "graphics_jni_helpers.h" + +#define gIndexByteSize 2 + +// A smart pointer that provides read only access to Java.nio.Buffer. This handles both +// direct and indrect buffers, allowing access to the underlying data in both +// situations. If passed a null buffer, we will throw NullPointerException, +// and c_data will return nullptr. +// +// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void * +// conversion. +class ScopedJavaNioBuffer { +public: + ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, jint size, jboolean isDirect) + : mEnv(env), mBuffer(buffer) { + if (buffer == nullptr) { + mDataBase = nullptr; + mData = nullptr; + jniThrowNullPointerException(env); + } else { + mArray = (jarray) nullptr; + if (isDirect) { + mData = getDirectBufferPointer(mEnv, mBuffer); + } else { + mData = setIndirectData(size); + } + } + } + + ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); } + + ~ScopedJavaNioBuffer() { reset(); } + + void reset() { + if (mDataBase) { + releasePointer(mEnv, mArray, mDataBase, JNI_FALSE); + mDataBase = nullptr; + } + } + + ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept { + if (this != &rhs) { + reset(); + + mEnv = rhs.mEnv; + mBuffer = rhs.mBuffer; + mDataBase = rhs.mDataBase; + mData = rhs.mData; + mArray = rhs.mArray; + rhs.mEnv = nullptr; + rhs.mData = nullptr; + rhs.mBuffer = nullptr; + rhs.mArray = nullptr; + rhs.mDataBase = nullptr; + } + return *this; + } + + const void* data() const { return mData; } + +private: + /** + * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data + * from a java.nio.Buffer. + */ + void* getDirectBufferPointer(JNIEnv* env, jobject buffer) { + if (buffer == nullptr) { + return nullptr; + } + + jint position; + jint limit; + jint elementSizeShift; + jlong pointer; + pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift); + if (pointer == 0) { + jniThrowException(mEnv, "java/lang/IllegalArgumentException", + "Must use a native order direct Buffer"); + return nullptr; + } + pointer += position << elementSizeShift; + return reinterpret_cast<void*>(pointer); + } + + static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) { + env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT); + } + + static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining, + jint* offset) { + jint position; + jint limit; + jint elementSizeShift; + + jlong pointer; + pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift); + *remaining = (limit - position) << elementSizeShift; + if (pointer != 0L) { + *array = nullptr; + pointer += position << elementSizeShift; + return reinterpret_cast<void*>(pointer); + } + + *array = jniGetNioBufferBaseArray(env, buffer); + *offset = jniGetNioBufferBaseArrayOffset(env, buffer); + return nullptr; + } + + /** + * This is a copy of + * static void android_glBufferData__IILjava_nio_Buffer_2I + * from com_google_android_gles_jni_GLImpl.cpp + */ + void* setIndirectData(jint size) { + jint exception; + const char* exceptionType; + const char* exceptionMessage; + jint bufferOffset = (jint)0; + jint remaining; + void* tempData; + + if (mBuffer) { + tempData = + (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset); + if (remaining < size) { + exception = 1; + exceptionType = "java/lang/IllegalArgumentException"; + exceptionMessage = "remaining() < size < needed"; + goto exit; + } + } + if (mBuffer && tempData == nullptr) { + mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0); + tempData = (void*)(mDataBase + bufferOffset); + } + return tempData; + exit: + if (mArray) { + releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE); + } + if (exception) { + jniThrowException(mEnv, exceptionType, exceptionMessage); + } + return nullptr; + } + + JNIEnv* mEnv; + + // Java Buffer data + void* mData; + jobject mBuffer; + + // Indirect Buffer Data + jarray mArray; + char* mDataBase; +}; + +class MeshUniformBuilder { +public: + struct MeshUniform { + template <typename T> + std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=( + const T& val) { + if (!fVar) { + SkDEBUGFAIL("Assigning to missing variable"); + } else if (sizeof(val) != fVar->sizeInBytes()) { + SkDEBUGFAIL("Incorrect value size"); + } else { + memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), &val, + szeof(val)); + } + } + + MeshUniform& operator=(const SkMatrix& val) { + if (!fVar) { + SkDEBUGFAIL("Assigning to missing variable"); + } else if (fVar->sizeInBytes() != 9 * sizeof(float)) { + SkDEBUGFAIL("Incorrect value size"); + } else { + float* data = + SkTAddOffset<float>(fOwner->writableUniformData(), (ptrdiff_t)fVar->offset); + data[0] = val.get(0); + data[1] = val.get(3); + data[2] = val.get(6); + data[3] = val.get(1); + data[4] = val.get(4); + data[5] = val.get(7); + data[6] = val.get(2); + data[7] = val.get(5); + data[8] = val.get(8); + } + return *this; + } + + template <typename T> + bool set(const T val[], const int count) { + static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable"); + if (!fVar) { + SkDEBUGFAIL("Assigning to missing variable"); + return false; + } else if (sizeof(T) * count != fVar->sizeInBytes()) { + SkDEBUGFAIL("Incorrect value size"); + return false; + } else { + memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), val, + sizeof(T) * count); + } + return true; + } + + MeshUniformBuilder* fOwner; + const SkRuntimeEffect::Uniform* fVar; + }; + MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; } + + explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) { + fMeshSpec = sk_sp(meshSpec); + fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize())); + } + + sk_sp<SkData> fUniforms; + +private: + void* writableUniformData() { + if (!fUniforms->unique()) { + fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size()); + } + return fUniforms->writable_data(); + } + + sk_sp<SkMeshSpecification> fMeshSpec; +}; + +struct MeshWrapper { + SkMesh mesh; + MeshUniformBuilder builder; +}; +#endif // FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_ diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp new file mode 100644 index 000000000000..ae9792df3d82 --- /dev/null +++ b/libs/hwui/jni/MeshSpecification.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2022 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. + */ + +#include <SkMesh.h> + +#include "GraphicsJNI.h" +#include "graphics_jni_helpers.h" + +namespace android { + +using Attribute = SkMeshSpecification::Attribute; +using Varying = SkMeshSpecification::Varying; + +static struct { + jclass clazz{}; + jfieldID type{}; + jfieldID offset{}; + jfieldID name{}; +} gAttributeInfo; + +static struct { + jclass clazz{}; + jfieldID type{}; + jfieldID name{}; +} gVaryingInfo; + +std::vector<Attribute> extractAttributes(JNIEnv* env, jobjectArray attributes) { + int size = env->GetArrayLength(attributes); + std::vector<Attribute> attVector; + attVector.reserve(size); + for (int i = 0; i < size; i++) { + jobject attribute = env->GetObjectArrayElement(attributes, i); + auto name = (jstring)env->GetObjectField(attribute, gAttributeInfo.name); + auto attName = ScopedUtfChars(env, name); + Attribute temp{Attribute::Type(env->GetIntField(attribute, gAttributeInfo.type)), + static_cast<size_t>(env->GetIntField(attribute, gAttributeInfo.offset)), + SkString(attName.c_str())}; + attVector.push_back(std::move(temp)); + } + return attVector; +} + +std::vector<Varying> extractVaryings(JNIEnv* env, jobjectArray varyings) { + int size = env->GetArrayLength(varyings); + std::vector<Varying> varyVector; + varyVector.reserve(size); + for (int i = 0; i < size; i++) { + jobject varying = env->GetObjectArrayElement(varyings, i); + auto name = (jstring)env->GetObjectField(varying, gVaryingInfo.name); + auto varyName = ScopedUtfChars(env, name); + Varying temp{Varying::Type(env->GetIntField(varying, gVaryingInfo.type)), + SkString(varyName.c_str())}; + varyVector.push_back(std::move(temp)); + } + + return varyVector; +} + +static jlong Make(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride, + jobjectArray varyingArray, jstring vertexShader, jstring fragmentShader) { + auto attributes = extractAttributes(env, attributeArray); + auto varyings = extractVaryings(env, varyingArray); + auto skVertexShader = ScopedUtfChars(env, vertexShader); + auto skFragmentShader = ScopedUtfChars(env, fragmentShader); + auto meshSpecResult = SkMeshSpecification::Make(attributes, vertexStride, varyings, + SkString(skVertexShader.c_str()), + SkString(skFragmentShader.c_str())); + if (meshSpecResult.specification.get() == nullptr) { + jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str()); + } + + return reinterpret_cast<jlong>(meshSpecResult.specification.release()); +} + +static jlong MakeWithCS(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride, + jobjectArray varyingArray, jstring vertexShader, jstring fragmentShader, + jlong colorSpace) { + auto attributes = extractAttributes(env, attributeArray); + auto varyings = extractVaryings(env, varyingArray); + auto skVertexShader = ScopedUtfChars(env, vertexShader); + auto skFragmentShader = ScopedUtfChars(env, fragmentShader); + auto meshSpecResult = SkMeshSpecification::Make( + attributes, vertexStride, varyings, SkString(skVertexShader.c_str()), + SkString(skFragmentShader.c_str()), GraphicsJNI::getNativeColorSpace(colorSpace)); + + if (meshSpecResult.specification.get() == nullptr) { + jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str()); + } + + return reinterpret_cast<jlong>(meshSpecResult.specification.release()); +} + +static jlong MakeWithAlpha(JNIEnv* env, jobject thiz, jobjectArray attributeArray, + jint vertexStride, jobjectArray varyingArray, jstring vertexShader, + jstring fragmentShader, jlong colorSpace, jint alphaType) { + auto attributes = extractAttributes(env, attributeArray); + auto varyings = extractVaryings(env, varyingArray); + auto skVertexShader = ScopedUtfChars(env, vertexShader); + auto skFragmentShader = ScopedUtfChars(env, fragmentShader); + auto meshSpecResult = SkMeshSpecification::Make( + attributes, vertexStride, varyings, SkString(skVertexShader.c_str()), + SkString(skFragmentShader.c_str()), GraphicsJNI::getNativeColorSpace(colorSpace), + SkAlphaType(alphaType)); + + if (meshSpecResult.specification.get() == nullptr) { + jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str()); + } + + return reinterpret_cast<jlong>(meshSpecResult.specification.release()); +} + +static void MeshSpecification_safeUnref(SkMeshSpecification* meshSpec) { + SkSafeUnref(meshSpec); +} + +static jlong getMeshSpecificationFinalizer() { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshSpecification_safeUnref)); +} + +static const JNINativeMethod gMeshSpecificationMethods[] = { + {"nativeGetFinalizer", "()J", (void*)getMeshSpecificationFinalizer}, + {"nativeMake", + "([Landroid/graphics/MeshSpecification$Attribute;I[Landroid/graphics/" + "MeshSpecification$Varying;" + "Ljava/lang/String;Ljava/lang/String;)J", + (void*)Make}, + {"nativeMakeWithCS", + "([Landroid/graphics/MeshSpecification$Attribute;I" + "[Landroid/graphics/MeshSpecification$Varying;Ljava/lang/String;Ljava/lang/String;J)J", + (void*)MakeWithCS}, + {"nativeMakeWithAlpha", + "([Landroid/graphics/MeshSpecification$Attribute;I" + "[Landroid/graphics/MeshSpecification$Varying;Ljava/lang/String;Ljava/lang/String;JI)J", + (void*)MakeWithAlpha}}; + +int register_android_graphics_MeshSpecification(JNIEnv* env) { + android::RegisterMethodsOrDie(env, "android/graphics/MeshSpecification", + gMeshSpecificationMethods, NELEM(gMeshSpecificationMethods)); + + gAttributeInfo.clazz = env->FindClass("android/graphics/MeshSpecification$Attribute"); + gAttributeInfo.type = env->GetFieldID(gAttributeInfo.clazz, "mType", "I"); + gAttributeInfo.offset = env->GetFieldID(gAttributeInfo.clazz, "mOffset", "I"); + gAttributeInfo.name = env->GetFieldID(gAttributeInfo.clazz, "mName", "Ljava/lang/String;"); + + gVaryingInfo.clazz = env->FindClass("android/graphics/MeshSpecification$Varying"); + gVaryingInfo.type = env->GetFieldID(gVaryingInfo.clazz, "mType", "I"); + gVaryingInfo.name = env->GetFieldID(gVaryingInfo.clazz, "mName", "Ljava/lang/String;"); + return 0; +} + +} // namespace android diff --git a/libs/hwui/jni/Movie.h b/libs/hwui/jni/Movie.h index 736890d5215e..02113dd58ec8 100644 --- a/libs/hwui/jni/Movie.h +++ b/libs/hwui/jni/Movie.h @@ -13,6 +13,7 @@ #include "SkBitmap.h" #include "SkCanvas.h" #include "SkRefCnt.h" +#include "SkTypes.h" class SkStreamRewindable; diff --git a/libs/hwui/jni/MovieImpl.cpp b/libs/hwui/jni/MovieImpl.cpp index ae9e04e617b0..abb75fa99c94 100644 --- a/libs/hwui/jni/MovieImpl.cpp +++ b/libs/hwui/jni/MovieImpl.cpp @@ -5,11 +5,12 @@ * found in the LICENSE file. */ #include "Movie.h" -#include "SkCanvas.h" -#include "SkPaint.h" +#include "SkBitmap.h" +#include "SkStream.h" +#include "SkTypes.h" // We should never see this in normal operation since our time values are -// 0-based. So we use it as a sentinal. +// 0-based. So we use it as a sentinel. #define UNINITIALIZED_MSEC ((SkMSec)-1) Movie::Movie() @@ -81,8 +82,6 @@ const SkBitmap& Movie::bitmap() //////////////////////////////////////////////////////////////////// -#include "SkStream.h" - Movie* Movie::DecodeMemory(const void* data, size_t length) { SkMemoryStream stream(data, length, false); return Movie::DecodeStream(&stream); diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp index 08fc80fbdafd..d50a8a22b5cb 100644 --- a/libs/hwui/jni/NinePatch.cpp +++ b/libs/hwui/jni/NinePatch.cpp @@ -24,8 +24,10 @@ #include <hwui/Paint.h> #include <utils/Log.h> +#include "SkBitmap.h" #include "SkCanvas.h" #include "SkLatticeIter.h" +#include "SkRect.h" #include "SkRegion.h" #include "GraphicsJNI.h" #include "NinePatchPeeker.h" diff --git a/libs/hwui/jni/NinePatchPeeker.cpp b/libs/hwui/jni/NinePatchPeeker.cpp index 9171fc687276..d85ede5dc6d2 100644 --- a/libs/hwui/jni/NinePatchPeeker.cpp +++ b/libs/hwui/jni/NinePatchPeeker.cpp @@ -16,7 +16,7 @@ #include "NinePatchPeeker.h" -#include <SkBitmap.h> +#include <SkScalar.h> #include <cutils/compiler.h> using namespace android; diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index f76863255153..b563627edad9 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -26,12 +26,14 @@ #include <nativehelper/ScopedPrimitiveArray.h> #include "SkColorFilter.h" +#include "SkColorSpace.h" #include "SkFont.h" #include "SkFontMetrics.h" #include "SkFontTypes.h" #include "SkMaskFilter.h" #include "SkPath.h" #include "SkPathEffect.h" +#include "SkPathUtils.h" #include "SkShader.h" #include "SkBlendMode.h" #include "unicode/uloc.h" @@ -496,17 +498,32 @@ namespace PaintGlue { return true; } - static jfloat doRunAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[], - jint start, jint count, jint bufSize, jboolean isRtl, jint offset) { + static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface, + const jchar buf[], jint start, jint count, jint bufSize, + jboolean isRtl, jint offset, jfloatArray advances, + jint advancesIndex) { + if (advances) { + size_t advancesLength = env->GetArrayLength(advances); + if ((size_t)(count + advancesIndex) > advancesLength) { + doThrowAIOOBE(env); + return 0; + } + } minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; - if (offset == start + count) { + if (offset == start + count && advances == nullptr) { return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, nullptr); } std::unique_ptr<float[]> advancesArray(new float[count]); MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, advancesArray.get()); - return minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset); + + float result = minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset); + if (advances) { + minikin::distributeAdvances(advancesArray.get(), buf, start, count); + env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get()); + } + return result; } static jfloat getRunAdvance___CIIIIZI_F(JNIEnv *env, jclass, jlong paintHandle, jcharArray text, @@ -514,9 +531,23 @@ namespace PaintGlue { const Paint* paint = reinterpret_cast<Paint*>(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); ScopedCharArrayRO textArray(env, text); - jfloat result = doRunAdvance(paint, typeface, textArray.get() + contextStart, - start - contextStart, end - start, contextEnd - contextStart, isRtl, - offset - contextStart); + jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart, + start - contextStart, end - start, contextEnd - contextStart, + isRtl, offset - contextStart, nullptr, 0); + return result; + } + + static jfloat getRunCharacterAdvance___CIIIIZI_FI_F(JNIEnv* env, jclass, jlong paintHandle, + jcharArray text, jint start, jint end, + jint contextStart, jint contextEnd, + jboolean isRtl, jint offset, + jfloatArray advances, jint advancesIndex) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + ScopedCharArrayRO textArray(env, text); + jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart, + start - contextStart, end - start, contextEnd - contextStart, + isRtl, offset - contextStart, advances, advancesIndex); return result; } @@ -779,7 +810,7 @@ namespace PaintGlue { Paint* obj = reinterpret_cast<Paint*>(objHandle); SkPath* src = reinterpret_cast<SkPath*>(srcHandle); SkPath* dst = reinterpret_cast<SkPath*>(dstHandle); - return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE; + return skpathutils::FillPathWithPaint(*src, *obj, dst) ? JNI_TRUE : JNI_FALSE; } static jlong setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) { @@ -1033,113 +1064,112 @@ namespace PaintGlue { }; // namespace PaintGlue static const JNINativeMethod methods[] = { - {"nGetNativeFinalizer", "()J", (void*) PaintGlue::getNativeFinalizer}, - {"nInit","()J", (void*) PaintGlue::init}, - {"nInitWithPaint","(J)J", (void*) PaintGlue::initWithPaint}, - {"nBreakText","(J[CIIFI[F)I", (void*) PaintGlue::breakTextC}, - {"nBreakText","(JLjava/lang/String;ZFI[F)I", (void*) PaintGlue::breakTextS}, - {"nGetTextAdvances","(J[CIIIII[FI)F", - (void*) PaintGlue::getTextAdvances___CIIIII_FI}, - {"nGetTextAdvances","(JLjava/lang/String;IIIII[FI)F", - (void*) PaintGlue::getTextAdvances__StringIIIII_FI}, - - {"nGetTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C}, - {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I", - (void*) PaintGlue::getTextRunCursor__String}, - {"nGetTextPath", "(JI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C}, - {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String}, - {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V", - (void*) PaintGlue::getStringBounds }, - {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V", - (void*) PaintGlue::getCharArrayBounds }, - {"nHasGlyph", "(JILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph }, - {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F}, - {"nGetOffsetForAdvance", "(J[CIIIIZF)I", - (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I}, - {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", - (void*)PaintGlue::getFontMetricsIntForText___C}, - {"nGetFontMetricsIntForText", - "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V", - (void*)PaintGlue::getFontMetricsIntForText___String}, - - // --------------- @FastNative ---------------------- - - {"nSetTextLocales","(JLjava/lang/String;)I", (void*) PaintGlue::setTextLocales}, - {"nSetFontFeatureSettings","(JLjava/lang/String;)V", - (void*) PaintGlue::setFontFeatureSettings}, - {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F", - (void*)PaintGlue::getFontMetrics}, - {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I", - (void*)PaintGlue::getFontMetricsInt}, - - // --------------- @CriticalNative ------------------ - - {"nReset","(J)V", (void*) PaintGlue::reset}, - {"nSet","(JJ)V", (void*) PaintGlue::assign}, - {"nGetFlags","(J)I", (void*) PaintGlue::getFlags}, - {"nSetFlags","(JI)V", (void*) PaintGlue::setFlags}, - {"nGetHinting","(J)I", (void*) PaintGlue::getHinting}, - {"nSetHinting","(JI)V", (void*) PaintGlue::setHinting}, - {"nSetAntiAlias","(JZ)V", (void*) PaintGlue::setAntiAlias}, - {"nSetSubpixelText","(JZ)V", (void*) PaintGlue::setSubpixelText}, - {"nSetLinearText","(JZ)V", (void*) PaintGlue::setLinearText}, - {"nSetUnderlineText","(JZ)V", (void*) PaintGlue::setUnderlineText}, - {"nSetStrikeThruText","(JZ)V", (void*) PaintGlue::setStrikeThruText}, - {"nSetFakeBoldText","(JZ)V", (void*) PaintGlue::setFakeBoldText}, - {"nSetFilterBitmap","(JZ)V", (void*) PaintGlue::setFilterBitmap}, - {"nSetDither","(JZ)V", (void*) PaintGlue::setDither}, - {"nGetStyle","(J)I", (void*) PaintGlue::getStyle}, - {"nSetStyle","(JI)V", (void*) PaintGlue::setStyle}, - {"nSetColor","(JI)V", (void*) PaintGlue::setColor}, - {"nSetColor","(JJJ)V", (void*) PaintGlue::setColorLong}, - {"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha}, - {"nGetStrokeWidth","(J)F", (void*) PaintGlue::getStrokeWidth}, - {"nSetStrokeWidth","(JF)V", (void*) PaintGlue::setStrokeWidth}, - {"nGetStrokeMiter","(J)F", (void*) PaintGlue::getStrokeMiter}, - {"nSetStrokeMiter","(JF)V", (void*) PaintGlue::setStrokeMiter}, - {"nGetStrokeCap","(J)I", (void*) PaintGlue::getStrokeCap}, - {"nSetStrokeCap","(JI)V", (void*) PaintGlue::setStrokeCap}, - {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin}, - {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin}, - {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath}, - {"nSetShader","(JJ)J", (void*) PaintGlue::setShader}, - {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter}, - {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode}, - {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect}, - {"nSetMaskFilter","(JJ)J", (void*) PaintGlue::setMaskFilter}, - {"nSetTypeface","(JJ)V", (void*) PaintGlue::setTypeface}, - {"nGetTextAlign","(J)I", (void*) PaintGlue::getTextAlign}, - {"nSetTextAlign","(JI)V", (void*) PaintGlue::setTextAlign}, - {"nSetTextLocalesByMinikinLocaleListId","(JI)V", - (void*) PaintGlue::setTextLocalesByMinikinLocaleListId}, - {"nIsElegantTextHeight","(J)Z", (void*) PaintGlue::isElegantTextHeight}, - {"nSetElegantTextHeight","(JZ)V", (void*) PaintGlue::setElegantTextHeight}, - {"nGetTextSize","(J)F", (void*) PaintGlue::getTextSize}, - {"nSetTextSize","(JF)V", (void*) PaintGlue::setTextSize}, - {"nGetTextScaleX","(J)F", (void*) PaintGlue::getTextScaleX}, - {"nSetTextScaleX","(JF)V", (void*) PaintGlue::setTextScaleX}, - {"nGetTextSkewX","(J)F", (void*) PaintGlue::getTextSkewX}, - {"nSetTextSkewX","(JF)V", (void*) PaintGlue::setTextSkewX}, - {"nGetLetterSpacing","(J)F", (void*) PaintGlue::getLetterSpacing}, - {"nSetLetterSpacing","(JF)V", (void*) PaintGlue::setLetterSpacing}, - {"nGetWordSpacing","(J)F", (void*) PaintGlue::getWordSpacing}, - {"nSetWordSpacing","(JF)V", (void*) PaintGlue::setWordSpacing}, - {"nGetStartHyphenEdit", "(J)I", (void*) PaintGlue::getStartHyphenEdit}, - {"nGetEndHyphenEdit", "(J)I", (void*) PaintGlue::getEndHyphenEdit}, - {"nSetStartHyphenEdit", "(JI)V", (void*) PaintGlue::setStartHyphenEdit}, - {"nSetEndHyphenEdit", "(JI)V", (void*) PaintGlue::setEndHyphenEdit}, - {"nAscent","(J)F", (void*) PaintGlue::ascent}, - {"nDescent","(J)F", (void*) PaintGlue::descent}, - {"nGetUnderlinePosition","(J)F", (void*) PaintGlue::getUnderlinePosition}, - {"nGetUnderlineThickness","(J)F", (void*) PaintGlue::getUnderlineThickness}, - {"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition}, - {"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness}, - {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer}, - {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}, - {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, + {"nGetNativeFinalizer", "()J", (void*)PaintGlue::getNativeFinalizer}, + {"nInit", "()J", (void*)PaintGlue::init}, + {"nInitWithPaint", "(J)J", (void*)PaintGlue::initWithPaint}, + {"nBreakText", "(J[CIIFI[F)I", (void*)PaintGlue::breakTextC}, + {"nBreakText", "(JLjava/lang/String;ZFI[F)I", (void*)PaintGlue::breakTextS}, + {"nGetTextAdvances", "(J[CIIIII[FI)F", (void*)PaintGlue::getTextAdvances___CIIIII_FI}, + {"nGetTextAdvances", "(JLjava/lang/String;IIIII[FI)F", + (void*)PaintGlue::getTextAdvances__StringIIIII_FI}, + + {"nGetTextRunCursor", "(J[CIIIII)I", (void*)PaintGlue::getTextRunCursor___C}, + {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I", + (void*)PaintGlue::getTextRunCursor__String}, + {"nGetTextPath", "(JI[CIIFFJ)V", (void*)PaintGlue::getTextPath___C}, + {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*)PaintGlue::getTextPath__String}, + {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V", + (void*)PaintGlue::getStringBounds}, + {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V", + (void*)PaintGlue::getCharArrayBounds}, + {"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph}, + {"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F}, + {"nGetRunCharacterAdvance", "(J[CIIIIZI[FI)F", + (void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F}, + {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I}, + {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___C}, + {"nGetFontMetricsIntForText", + "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___String}, + + // --------------- @FastNative ---------------------- + + {"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales}, + {"nSetFontFeatureSettings", "(JLjava/lang/String;)V", + (void*)PaintGlue::setFontFeatureSettings}, + {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F", + (void*)PaintGlue::getFontMetrics}, + {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I", + (void*)PaintGlue::getFontMetricsInt}, + + // --------------- @CriticalNative ------------------ + + {"nReset", "(J)V", (void*)PaintGlue::reset}, + {"nSet", "(JJ)V", (void*)PaintGlue::assign}, + {"nGetFlags", "(J)I", (void*)PaintGlue::getFlags}, + {"nSetFlags", "(JI)V", (void*)PaintGlue::setFlags}, + {"nGetHinting", "(J)I", (void*)PaintGlue::getHinting}, + {"nSetHinting", "(JI)V", (void*)PaintGlue::setHinting}, + {"nSetAntiAlias", "(JZ)V", (void*)PaintGlue::setAntiAlias}, + {"nSetSubpixelText", "(JZ)V", (void*)PaintGlue::setSubpixelText}, + {"nSetLinearText", "(JZ)V", (void*)PaintGlue::setLinearText}, + {"nSetUnderlineText", "(JZ)V", (void*)PaintGlue::setUnderlineText}, + {"nSetStrikeThruText", "(JZ)V", (void*)PaintGlue::setStrikeThruText}, + {"nSetFakeBoldText", "(JZ)V", (void*)PaintGlue::setFakeBoldText}, + {"nSetFilterBitmap", "(JZ)V", (void*)PaintGlue::setFilterBitmap}, + {"nSetDither", "(JZ)V", (void*)PaintGlue::setDither}, + {"nGetStyle", "(J)I", (void*)PaintGlue::getStyle}, + {"nSetStyle", "(JI)V", (void*)PaintGlue::setStyle}, + {"nSetColor", "(JI)V", (void*)PaintGlue::setColor}, + {"nSetColor", "(JJJ)V", (void*)PaintGlue::setColorLong}, + {"nSetAlpha", "(JI)V", (void*)PaintGlue::setAlpha}, + {"nGetStrokeWidth", "(J)F", (void*)PaintGlue::getStrokeWidth}, + {"nSetStrokeWidth", "(JF)V", (void*)PaintGlue::setStrokeWidth}, + {"nGetStrokeMiter", "(J)F", (void*)PaintGlue::getStrokeMiter}, + {"nSetStrokeMiter", "(JF)V", (void*)PaintGlue::setStrokeMiter}, + {"nGetStrokeCap", "(J)I", (void*)PaintGlue::getStrokeCap}, + {"nSetStrokeCap", "(JI)V", (void*)PaintGlue::setStrokeCap}, + {"nGetStrokeJoin", "(J)I", (void*)PaintGlue::getStrokeJoin}, + {"nSetStrokeJoin", "(JI)V", (void*)PaintGlue::setStrokeJoin}, + {"nGetFillPath", "(JJJ)Z", (void*)PaintGlue::getFillPath}, + {"nSetShader", "(JJ)J", (void*)PaintGlue::setShader}, + {"nSetColorFilter", "(JJ)J", (void*)PaintGlue::setColorFilter}, + {"nSetXfermode", "(JI)V", (void*)PaintGlue::setXfermode}, + {"nSetPathEffect", "(JJ)J", (void*)PaintGlue::setPathEffect}, + {"nSetMaskFilter", "(JJ)J", (void*)PaintGlue::setMaskFilter}, + {"nSetTypeface", "(JJ)V", (void*)PaintGlue::setTypeface}, + {"nGetTextAlign", "(J)I", (void*)PaintGlue::getTextAlign}, + {"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign}, + {"nSetTextLocalesByMinikinLocaleListId", "(JI)V", + (void*)PaintGlue::setTextLocalesByMinikinLocaleListId}, + {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight}, + {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight}, + {"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize}, + {"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize}, + {"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX}, + {"nSetTextScaleX", "(JF)V", (void*)PaintGlue::setTextScaleX}, + {"nGetTextSkewX", "(J)F", (void*)PaintGlue::getTextSkewX}, + {"nSetTextSkewX", "(JF)V", (void*)PaintGlue::setTextSkewX}, + {"nGetLetterSpacing", "(J)F", (void*)PaintGlue::getLetterSpacing}, + {"nSetLetterSpacing", "(JF)V", (void*)PaintGlue::setLetterSpacing}, + {"nGetWordSpacing", "(J)F", (void*)PaintGlue::getWordSpacing}, + {"nSetWordSpacing", "(JF)V", (void*)PaintGlue::setWordSpacing}, + {"nGetStartHyphenEdit", "(J)I", (void*)PaintGlue::getStartHyphenEdit}, + {"nGetEndHyphenEdit", "(J)I", (void*)PaintGlue::getEndHyphenEdit}, + {"nSetStartHyphenEdit", "(JI)V", (void*)PaintGlue::setStartHyphenEdit}, + {"nSetEndHyphenEdit", "(JI)V", (void*)PaintGlue::setEndHyphenEdit}, + {"nAscent", "(J)F", (void*)PaintGlue::ascent}, + {"nDescent", "(J)F", (void*)PaintGlue::descent}, + {"nGetUnderlinePosition", "(J)F", (void*)PaintGlue::getUnderlinePosition}, + {"nGetUnderlineThickness", "(J)F", (void*)PaintGlue::getUnderlineThickness}, + {"nGetStrikeThruPosition", "(J)F", (void*)PaintGlue::getStrikeThruPosition}, + {"nGetStrikeThruThickness", "(J)F", (void*)PaintGlue::getStrikeThruThickness}, + {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer}, + {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}, + {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, }; - int register_android_graphics_Paint(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods)); } diff --git a/libs/hwui/jni/Path.cpp b/libs/hwui/jni/Path.cpp index d67bcf221681..3694ce07b972 100644 --- a/libs/hwui/jni/Path.cpp +++ b/libs/hwui/jni/Path.cpp @@ -102,6 +102,18 @@ public: obj->rQuadTo(dx1, dy1, dx2, dy2); } + static void conicTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, + jfloat y2, jfloat weight) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->conicTo(x1, y1, x2, y2, weight); + } + + static void rConicTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx1, jfloat dy1, + jfloat dx2, jfloat dy2, jfloat weight) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->rConicTo(dx1, dy1, dx2, dy2, weight); + } + static void cubicTo__FFFFFF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); @@ -209,6 +221,14 @@ public: obj->setLastPt(dx, dy); } + static jboolean interpolate(JNIEnv* env, jclass clazz, jlong startHandle, jlong endHandle, + jfloat t, jlong interpolatedHandle) { + SkPath* startPath = reinterpret_cast<SkPath*>(startHandle); + SkPath* endPath = reinterpret_cast<SkPath*>(endHandle); + SkPath* interpolatedPath = reinterpret_cast<SkPath*>(interpolatedHandle); + return startPath->interpolate(*endPath, t, interpolatedPath); + } + static void transform__MatrixPath(JNIEnv* env, jclass clazz, jlong objHandle, jlong matrixHandle, jlong dstHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); @@ -473,6 +493,16 @@ public: // ---------------- @CriticalNative ------------------------- + static jint getGenerationID(CRITICAL_JNI_PARAMS_COMMA jlong pathHandle) { + return (reinterpret_cast<SkPath*>(pathHandle)->getGenerationID()); + } + + static jboolean isInterpolatable(CRITICAL_JNI_PARAMS_COMMA jlong startHandle, jlong endHandle) { + SkPath* startPath = reinterpret_cast<SkPath*>(startHandle); + SkPath* endPath = reinterpret_cast<SkPath*>(endHandle); + return startPath->isInterpolatable(*endPath); + } + static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->reset(); @@ -506,48 +536,53 @@ public: }; static const JNINativeMethod methods[] = { - {"nInit","()J", (void*) SkPathGlue::init}, - {"nInit","(J)J", (void*) SkPathGlue::init_Path}, - {"nGetFinalizer", "()J", (void*) SkPathGlue::getFinalizer}, - {"nSet","(JJ)V", (void*) SkPathGlue::set}, - {"nComputeBounds","(JLandroid/graphics/RectF;)V", (void*) SkPathGlue::computeBounds}, - {"nIncReserve","(JI)V", (void*) SkPathGlue::incReserve}, - {"nMoveTo","(JFF)V", (void*) SkPathGlue::moveTo__FF}, - {"nRMoveTo","(JFF)V", (void*) SkPathGlue::rMoveTo}, - {"nLineTo","(JFF)V", (void*) SkPathGlue::lineTo__FF}, - {"nRLineTo","(JFF)V", (void*) SkPathGlue::rLineTo}, - {"nQuadTo","(JFFFF)V", (void*) SkPathGlue::quadTo__FFFF}, - {"nRQuadTo","(JFFFF)V", (void*) SkPathGlue::rQuadTo}, - {"nCubicTo","(JFFFFFF)V", (void*) SkPathGlue::cubicTo__FFFFFF}, - {"nRCubicTo","(JFFFFFF)V", (void*) SkPathGlue::rCubicTo}, - {"nArcTo","(JFFFFFFZ)V", (void*) SkPathGlue::arcTo}, - {"nClose","(J)V", (void*) SkPathGlue::close}, - {"nAddRect","(JFFFFI)V", (void*) SkPathGlue::addRect}, - {"nAddOval","(JFFFFI)V", (void*) SkPathGlue::addOval}, - {"nAddCircle","(JFFFI)V", (void*) SkPathGlue::addCircle}, - {"nAddArc","(JFFFFFF)V", (void*) SkPathGlue::addArc}, - {"nAddRoundRect","(JFFFFFFI)V", (void*) SkPathGlue::addRoundRectXY}, - {"nAddRoundRect","(JFFFF[FI)V", (void*) SkPathGlue::addRoundRect8}, - {"nAddPath","(JJFF)V", (void*) SkPathGlue::addPath__PathFF}, - {"nAddPath","(JJ)V", (void*) SkPathGlue::addPath__Path}, - {"nAddPath","(JJJ)V", (void*) SkPathGlue::addPath__PathMatrix}, - {"nOffset","(JFF)V", (void*) SkPathGlue::offset__FF}, - {"nSetLastPoint","(JFF)V", (void*) SkPathGlue::setLastPoint}, - {"nTransform","(JJJ)V", (void*) SkPathGlue::transform__MatrixPath}, - {"nTransform","(JJ)V", (void*) SkPathGlue::transform__Matrix}, - {"nOp","(JJIJ)Z", (void*) SkPathGlue::op}, - {"nApproximate", "(JF)[F", (void*) SkPathGlue::approximate}, - - // ------- @FastNative below here ---------------------- - {"nIsRect","(JLandroid/graphics/RectF;)Z", (void*) SkPathGlue::isRect}, - - // ------- @CriticalNative below here ------------------ - {"nReset","(J)V", (void*) SkPathGlue::reset}, - {"nRewind","(J)V", (void*) SkPathGlue::rewind}, - {"nIsEmpty","(J)Z", (void*) SkPathGlue::isEmpty}, - {"nIsConvex","(J)Z", (void*) SkPathGlue::isConvex}, - {"nGetFillType","(J)I", (void*) SkPathGlue::getFillType}, - {"nSetFillType","(JI)V", (void*) SkPathGlue::setFillType}, + {"nInit", "()J", (void*)SkPathGlue::init}, + {"nInit", "(J)J", (void*)SkPathGlue::init_Path}, + {"nGetFinalizer", "()J", (void*)SkPathGlue::getFinalizer}, + {"nSet", "(JJ)V", (void*)SkPathGlue::set}, + {"nComputeBounds", "(JLandroid/graphics/RectF;)V", (void*)SkPathGlue::computeBounds}, + {"nIncReserve", "(JI)V", (void*)SkPathGlue::incReserve}, + {"nMoveTo", "(JFF)V", (void*)SkPathGlue::moveTo__FF}, + {"nRMoveTo", "(JFF)V", (void*)SkPathGlue::rMoveTo}, + {"nLineTo", "(JFF)V", (void*)SkPathGlue::lineTo__FF}, + {"nRLineTo", "(JFF)V", (void*)SkPathGlue::rLineTo}, + {"nQuadTo", "(JFFFF)V", (void*)SkPathGlue::quadTo__FFFF}, + {"nRQuadTo", "(JFFFF)V", (void*)SkPathGlue::rQuadTo}, + {"nConicTo", "(JFFFFF)V", (void*)SkPathGlue::conicTo}, + {"nRConicTo", "(JFFFFF)V", (void*)SkPathGlue::rConicTo}, + {"nCubicTo", "(JFFFFFF)V", (void*)SkPathGlue::cubicTo__FFFFFF}, + {"nRCubicTo", "(JFFFFFF)V", (void*)SkPathGlue::rCubicTo}, + {"nArcTo", "(JFFFFFFZ)V", (void*)SkPathGlue::arcTo}, + {"nClose", "(J)V", (void*)SkPathGlue::close}, + {"nAddRect", "(JFFFFI)V", (void*)SkPathGlue::addRect}, + {"nAddOval", "(JFFFFI)V", (void*)SkPathGlue::addOval}, + {"nAddCircle", "(JFFFI)V", (void*)SkPathGlue::addCircle}, + {"nAddArc", "(JFFFFFF)V", (void*)SkPathGlue::addArc}, + {"nAddRoundRect", "(JFFFFFFI)V", (void*)SkPathGlue::addRoundRectXY}, + {"nAddRoundRect", "(JFFFF[FI)V", (void*)SkPathGlue::addRoundRect8}, + {"nAddPath", "(JJFF)V", (void*)SkPathGlue::addPath__PathFF}, + {"nAddPath", "(JJ)V", (void*)SkPathGlue::addPath__Path}, + {"nAddPath", "(JJJ)V", (void*)SkPathGlue::addPath__PathMatrix}, + {"nInterpolate", "(JJFJ)Z", (void*)SkPathGlue::interpolate}, + {"nOffset", "(JFF)V", (void*)SkPathGlue::offset__FF}, + {"nSetLastPoint", "(JFF)V", (void*)SkPathGlue::setLastPoint}, + {"nTransform", "(JJJ)V", (void*)SkPathGlue::transform__MatrixPath}, + {"nTransform", "(JJ)V", (void*)SkPathGlue::transform__Matrix}, + {"nOp", "(JJIJ)Z", (void*)SkPathGlue::op}, + {"nApproximate", "(JF)[F", (void*)SkPathGlue::approximate}, + + // ------- @FastNative below here ---------------------- + {"nIsRect", "(JLandroid/graphics/RectF;)Z", (void*)SkPathGlue::isRect}, + + // ------- @CriticalNative below here ------------------ + {"nGetGenerationID", "(J)I", (void*)SkPathGlue::getGenerationID}, + {"nIsInterpolatable", "(JJ)Z", (void*)SkPathGlue::isInterpolatable}, + {"nReset", "(J)V", (void*)SkPathGlue::reset}, + {"nRewind", "(J)V", (void*)SkPathGlue::rewind}, + {"nIsEmpty", "(J)Z", (void*)SkPathGlue::isEmpty}, + {"nIsConvex", "(J)Z", (void*)SkPathGlue::isConvex}, + {"nGetFillType", "(J)I", (void*)SkPathGlue::getFillType}, + {"nSetFillType", "(JI)V", (void*)SkPathGlue::setFillType}, }; int register_android_graphics_Path(JNIEnv* env) { diff --git a/libs/hwui/jni/PathIterator.cpp b/libs/hwui/jni/PathIterator.cpp new file mode 100644 index 000000000000..3884342d8d37 --- /dev/null +++ b/libs/hwui/jni/PathIterator.cpp @@ -0,0 +1,81 @@ +/* libs/android_runtime/android/graphics/PathMeasure.cpp +** +** Copyright 2007, 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. +*/ + +#include <log/log.h> + +#include "GraphicsJNI.h" +#include "SkPath.h" +#include "SkPoint.h" + +namespace android { + +class SkPathIteratorGlue { +public: + static void finalizer(SkPath::RawIter* obj) { delete obj; } + + static jlong getFinalizer(JNIEnv* env, jclass clazz) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&finalizer)); + } + + static jlong create(JNIEnv* env, jobject clazz, jlong pathHandle) { + const SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + return reinterpret_cast<jlong>(new SkPath::RawIter(*path)); + } + + // ---------------- @CriticalNative ------------------------- + + static jint peek(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle) { + SkPath::RawIter* iterator = reinterpret_cast<SkPath::RawIter*>(iteratorHandle); + return iterator->peek(); + } + + static jint next(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle, jlong pointsArray) { + static_assert(SkPath::kMove_Verb == 0, "SkPath::Verb unexpected index"); + static_assert(SkPath::kLine_Verb == 1, "SkPath::Verb unexpected index"); + static_assert(SkPath::kQuad_Verb == 2, "SkPath::Verb unexpected index"); + static_assert(SkPath::kConic_Verb == 3, "SkPath::Verb unexpected index"); + static_assert(SkPath::kCubic_Verb == 4, "SkPath::Verb unexpected index"); + static_assert(SkPath::kClose_Verb == 5, "SkPath::Verb unexpected index"); + static_assert(SkPath::kDone_Verb == 6, "SkPath::Verb unexpected index"); + + SkPath::RawIter* iterator = reinterpret_cast<SkPath::RawIter*>(iteratorHandle); + float* points = reinterpret_cast<float*>(pointsArray); + SkPath::Verb verb = + static_cast<SkPath::Verb>(iterator->next(reinterpret_cast<SkPoint*>(points))); + if (verb == SkPath::kConic_Verb) { + float weight = iterator->conicWeight(); + points[6] = weight; + } + return static_cast<int>(verb); + } +}; + +static const JNINativeMethod methods[] = { + {"nCreate", "(J)J", (void*)SkPathIteratorGlue::create}, + {"nGetFinalizer", "()J", (void*)SkPathIteratorGlue::getFinalizer}, + + // ------- @CriticalNative below here ------------------ + + {"nPeek", "(J)I", (void*)SkPathIteratorGlue::peek}, + {"nNext", "(JJ)I", (void*)SkPathIteratorGlue::next}, +}; + +int register_android_graphics_PathIterator(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/PathIterator", methods, NELEM(methods)); +} + +} // namespace android diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp index 213f35a81b88..f3db1705e694 100644 --- a/libs/hwui/jni/RenderEffect.cpp +++ b/libs/hwui/jni/RenderEffect.cpp @@ -15,6 +15,7 @@ */ #include "Bitmap.h" #include "GraphicsJNI.h" +#include "SkBlendMode.h" #include "SkImageFilter.h" #include "SkImageFilters.h" #include "graphics_jni_helpers.h" diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index 0bbd8a8cf97c..fa8e2e79c831 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -2,11 +2,21 @@ #define LOG_TAG "ShaderJNI" #include "GraphicsJNI.h" +#include "SkBitmap.h" +#include "SkBlendMode.h" +#include "SkColor.h" #include "SkColorFilter.h" #include "SkGradientShader.h" +#include "SkImage.h" #include "SkImagePriv.h" +#include "SkMatrix.h" +#include "SkPoint.h" +#include "SkRefCnt.h" +#include "SkSamplingOptions.h" +#include "SkScalar.h" #include "SkShader.h" -#include "SkBlendMode.h" +#include "SkString.h" +#include "SkTileMode.h" #include "include/effects/SkRuntimeEffect.h" #include <vector> @@ -16,7 +26,7 @@ using namespace android::uirenderer; /** * By default Skia gradients will interpolate their colors in unpremul space * and then premultiply each of the results. We must set this flag to preserve - * backwards compatiblity by premultiplying the colors of the gradient first, + * backwards compatibility by premultiplying the colors of the gradient first, * and then interpolating between them. */ static const uint32_t sGradientShaderFlags = SkGradientShader::kInterpolateColorsInPremul_Flag; diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp index d86d9ee56f4c..209b35c5537c 100644 --- a/libs/hwui/jni/Typeface.cpp +++ b/libs/hwui/jni/Typeface.cpp @@ -20,18 +20,21 @@ #include <minikin/FontCollection.h> #include <minikin/FontFamily.h> #include <minikin/FontFileParser.h> +#include <minikin/LocaleList.h> +#include <minikin/MinikinFontFactory.h> #include <minikin/SystemFonts.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/ScopedUtfChars.h> + +#include <mutex> +#include <unordered_map> + #include "FontUtils.h" #include "GraphicsJNI.h" #include "SkData.h" #include "SkTypeface.h" #include "fonts/Font.h" -#include <mutex> -#include <unordered_map> - #ifdef __ANDROID__ #include <sys/stat.h> #endif @@ -106,27 +109,14 @@ static jint Typeface_getWeight(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) { static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray, jlong fallbackPtr, int weight, int italic) { ScopedLongArrayRO families(env, familyArray); - std::vector<std::shared_ptr<minikin::FontFamily>> familyVec; Typeface* typeface = (fallbackPtr == 0) ? nullptr : toTypeface(fallbackPtr); - if (typeface != nullptr) { - const std::vector<std::shared_ptr<minikin::FontFamily>>& fallbackFamilies = - toTypeface(fallbackPtr)->fFontCollection->getFamilies(); - familyVec.reserve(families.size() + fallbackFamilies.size()); - for (size_t i = 0; i < families.size(); i++) { - FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]); - familyVec.emplace_back(family->family); - } - for (size_t i = 0; i < fallbackFamilies.size(); i++) { - familyVec.emplace_back(fallbackFamilies[i]); - } - } else { - familyVec.reserve(families.size()); - for (size_t i = 0; i < families.size(); i++) { - FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]); - familyVec.emplace_back(family->family); - } + std::vector<std::shared_ptr<minikin::FontFamily>> familyVec; + familyVec.reserve(families.size()); + for (size_t i = 0; i < families.size(); i++) { + FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]); + familyVec.emplace_back(family->family); } - return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic)); + return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic, typeface)); } // CriticalNative @@ -137,15 +127,13 @@ static void Typeface_setDefault(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) { static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle) { Typeface* face = toTypeface(faceHandle); - const std::unordered_set<minikin::AxisTag>& tagSet = face->fFontCollection->getSupportedTags(); - const size_t length = tagSet.size(); + const size_t length = face->fFontCollection->getSupportedAxesCount(); if (length == 0) { return nullptr; } std::vector<jint> tagVec(length); - int index = 0; - for (const auto& tag : tagSet) { - tagVec[index++] = tag; + for (size_t i = 0; i < length; i++) { + tagVec[i] = face->fFontCollection->getSupportedAxisAt(i); } std::sort(tagVec.begin(), tagVec.end()); const jintArray result = env->NewIntArray(length); @@ -204,9 +192,18 @@ static sk_sp<SkData> makeSkDataCached(const std::string& path, bool hasVerity) { return entry; } -static std::shared_ptr<minikin::MinikinFont> loadMinikinFontSkia(minikin::BufferReader); +class MinikinFontSkiaFactory : minikin::MinikinFontFactory { +private: + MinikinFontSkiaFactory() : MinikinFontFactory() { MinikinFontFactory::setInstance(this); } + +public: + static void init() { static MinikinFontSkiaFactory factory; } + void skip(minikin::BufferReader* reader) const override; + std::shared_ptr<minikin::MinikinFont> create(minikin::BufferReader reader) const override; + void write(minikin::BufferWriter* writer, const minikin::MinikinFont* typeface) const override; +}; -static minikin::Font::TypefaceLoader* readMinikinFontSkia(minikin::BufferReader* reader) { +void MinikinFontSkiaFactory::skip(minikin::BufferReader* reader) const { // Advance reader's position. reader->skipString(); // fontPath reader->skip<int>(); // fontIndex @@ -216,10 +213,10 @@ static minikin::Font::TypefaceLoader* readMinikinFontSkia(minikin::BufferReader* reader->skip<uint32_t>(); // expectedFontRevision reader->skipString(); // expectedPostScriptName } - return &loadMinikinFontSkia; } -static std::shared_ptr<minikin::MinikinFont> loadMinikinFontSkia(minikin::BufferReader reader) { +std::shared_ptr<minikin::MinikinFont> MinikinFontSkiaFactory::create( + minikin::BufferReader reader) const { std::string_view fontPath = reader.readString(); std::string path(fontPath.data(), fontPath.size()); ATRACE_FORMAT("Loading font %s", path.c_str()); @@ -268,8 +265,8 @@ static std::shared_ptr<minikin::MinikinFont> loadMinikinFontSkia(minikin::Buffer return minikinFont; } -static void writeMinikinFontSkia(minikin::BufferWriter* writer, - const minikin::MinikinFont* typeface) { +void MinikinFontSkiaFactory::write(minikin::BufferWriter* writer, + const minikin::MinikinFont* typeface) const { // When you change the format of font metadata, please update code to parse // typefaceMetadataReader() in // frameworks/base/libs/hwui/jni/fonts/Font.cpp too. @@ -293,7 +290,9 @@ static void writeMinikinFontSkia(minikin::BufferWriter* writer, } } -static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongArray faceHandles) { +static jint Typeface_writeTypefaces(JNIEnv* env, jobject, jobject buffer, jint position, + jlongArray faceHandles) { + MinikinFontSkiaFactory::init(); ScopedLongArrayRO faces(env, faceHandles); std::vector<Typeface*> typefaces; typefaces.reserve(faces.size()); @@ -301,7 +300,12 @@ static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongA typefaces.push_back(toTypeface(faces[i])); } void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer); - minikin::BufferWriter writer(addr); + if (addr != nullptr && + reinterpret_cast<intptr_t>(addr) % minikin::BufferReader::kMaxAlignment != 0) { + ALOGE("addr (%p) must be aligned at kMaxAlignment, but it was not.", addr); + return 0; + } + minikin::BufferWriter writer(addr, position); std::vector<std::shared_ptr<minikin::FontCollection>> fontCollections; std::unordered_map<std::shared_ptr<minikin::FontCollection>, size_t> fcToIndex; for (Typeface* typeface : typefaces) { @@ -310,7 +314,7 @@ static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongA fontCollections.push_back(typeface->fFontCollection); } } - minikin::FontCollection::writeVector<writeMinikinFontSkia>(&writer, fontCollections); + minikin::FontCollection::writeVector(&writer, fontCollections); writer.write<uint32_t>(typefaces.size()); for (Typeface* typeface : typefaces) { writer.write<uint32_t>(fcToIndex.find(typeface->fFontCollection)->second); @@ -321,12 +325,20 @@ static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongA return static_cast<jint>(writer.size()); } -static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) { +static jlongArray Typeface_readTypefaces(JNIEnv* env, jobject, jobject buffer, jint position) { + MinikinFontSkiaFactory::init(); void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer); - if (addr == nullptr) return nullptr; - minikin::BufferReader reader(addr); + if (addr == nullptr) { + ALOGE("Passed a null buffer."); + return nullptr; + } + if (reinterpret_cast<intptr_t>(addr) % minikin::BufferReader::kMaxAlignment != 0) { + ALOGE("addr (%p) must be aligned at kMaxAlignment, but it was not.", addr); + return nullptr; + } + minikin::BufferReader reader(addr, position); std::vector<std::shared_ptr<minikin::FontCollection>> fontCollections = - minikin::FontCollection::readVector<readMinikinFontSkia>(&reader); + minikin::FontCollection::readVector(&reader); uint32_t typefaceCount = reader.read<uint32_t>(); std::vector<jlong> faceHandles; faceHandles.reserve(typefaceCount); @@ -343,7 +355,6 @@ static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) { return result; } - static void Typeface_forceSetStaticFinalField(JNIEnv *env, jclass cls, jstring fieldName, jobject typeface) { ScopedUtfChars fieldNameChars(env, fieldName); @@ -356,18 +367,6 @@ static void Typeface_forceSetStaticFinalField(JNIEnv *env, jclass cls, jstring f env->SetStaticObjectField(cls, fid, typeface); } -// Critical Native -static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) { - return toTypeface(faceHandle)->fFontCollection->getFamilies().size(); -} - -// Critical Native -static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) { - std::shared_ptr<minikin::FontFamily> family = - toTypeface(faceHandle)->fFontCollection->getFamilies()[index]; - return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family))); -} - // Regular JNI static void Typeface_warmUpCache(JNIEnv* env, jobject, jstring jFilePath) { ScopedUtfChars filePath(env, jFilePath); @@ -380,6 +379,12 @@ static void Typeface_addFontCollection(CRITICAL_JNI_PARAMS_COMMA jlong faceHandl minikin::SystemFonts::addFontMap(std::move(collection)); } +// Fast Native +static void Typeface_registerLocaleList(JNIEnv* env, jobject, jstring jLocales) { + ScopedUtfChars locales(env, jLocales); + minikin::registerLocaleList(locales.c_str()); +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gTypefaceMethods[] = { @@ -397,14 +402,13 @@ static const JNINativeMethod gTypefaceMethods[] = { {"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes}, {"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V", (void*)Typeface_registerGenericFamily}, - {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces}, - {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces}, + {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;I[J)I", (void*)Typeface_writeTypefaces}, + {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;I)[J", (void*)Typeface_readTypefaces}, {"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V", (void*)Typeface_forceSetStaticFinalField}, - {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize}, - {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily}, {"nativeWarmUpCache", "(Ljava/lang/String;)V", (void*)Typeface_warmUpCache}, {"nativeAddFontCollections", "(J)V", (void*)Typeface_addFontCollection}, + {"nativeRegisterLocaleList", "(Ljava/lang/String;)V", (void*)Typeface_registerLocaleList}, }; int register_android_graphics_Typeface(JNIEnv* env) diff --git a/libs/hwui/jni/Utils.h b/libs/hwui/jni/Utils.h index 6cdf44d85a5a..f6e3a0eeaa0e 100644 --- a/libs/hwui/jni/Utils.h +++ b/libs/hwui/jni/Utils.h @@ -17,8 +17,11 @@ #ifndef _ANDROID_GRAPHICS_UTILS_H_ #define _ANDROID_GRAPHICS_UTILS_H_ +#include "SkRefCnt.h" #include "SkStream.h" +class SkData; + #include <jni.h> #include <androidfw/Asset.h> diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp index d64d38a815f6..1c5f126d8672 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.cpp +++ b/libs/hwui/jni/YuvToJpegEncoder.cpp @@ -1,5 +1,7 @@ #include "CreateJavaOutputStreamAdaptor.h" #include "SkJPEGWriteUtility.h" +#include "SkStream.h" +#include "SkTypes.h" #include "YuvToJpegEncoder.h" #include <ui/PixelFormat.h> #include <hardware/hardware.h> diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h index 7e7b935df276..a69726b17e9d 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.h +++ b/libs/hwui/jni/YuvToJpegEncoder.h @@ -1,13 +1,13 @@ #ifndef _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ #define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ -#include "SkTypes.h" -#include "SkStream.h" extern "C" { #include "jpeglib.h" #include "jerror.h" } +class SkWStream; + class YuvToJpegEncoder { public: /** Create an encoder based on the YUV format. diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp index 132234b38003..8a4d4e17edb1 100644 --- a/libs/hwui/jni/android_graphics_Canvas.cpp +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -21,6 +21,7 @@ #else #define __ANDROID_API_P__ 28 #endif +#include <Mesh.h> #include <androidfw/ResourceTypes.h> #include <hwui/Canvas.h> #include <hwui/Paint.h> @@ -30,12 +31,24 @@ #include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/ScopedStringChars.h> -#include "FontUtils.h" #include "Bitmap.h" +#include "FontUtils.h" +#include "SkBitmap.h" +#include "SkBlendMode.h" +#include "SkClipOp.h" +#include "SkColor.h" +#include "SkColorSpace.h" #include "SkGraphics.h" +#include "SkImageInfo.h" +#include "SkMatrix.h" +#include "SkPath.h" +#include "SkPoint.h" +#include "SkRRect.h" +#include "SkRect.h" +#include "SkRefCnt.h" #include "SkRegion.h" +#include "SkScalar.h" #include "SkVertices.h" -#include "SkRRect.h" namespace minikin { class MeasuredText; @@ -431,6 +444,14 @@ static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle, blendMode, *paint); } +static void drawMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong meshHandle, jint modeHandle, + jlong paintHandle) { + const SkMesh mesh = reinterpret_cast<MeshWrapper*>(meshHandle)->mesh; + SkBlendMode blendMode = static_cast<SkBlendMode>(modeHandle); + SkPaint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawMesh(mesh, SkBlender::Mode(blendMode), *paint); +} + static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, jlong chunkHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jlong paintHandle, jint dstDensity, jint srcDensity) { @@ -701,9 +722,10 @@ static void setCompatibilityVersion(JNIEnv* env, jobject, jint apiLevel) { } static void punchHole(JNIEnv* env, jobject, jlong canvasPtr, jfloat left, jfloat top, jfloat right, - jfloat bottom, jfloat rx, jfloat ry) { + jfloat bottom, jfloat rx, jfloat ry, jfloat alpha) { auto canvas = reinterpret_cast<Canvas*>(canvasPtr); - canvas->punchHole(SkRRect::MakeRectXY(SkRect::MakeLTRB(left, top, right, bottom), rx, ry)); + canvas->punchHole(SkRRect::MakeRectXY(SkRect::MakeLTRB(left, top, right, bottom), rx, ry), + alpha); } }; // namespace CanvasJNI @@ -748,38 +770,38 @@ static const JNINativeMethod gMethods[] = { // If called from Canvas these are regular JNI // If called from DisplayListCanvas they are @FastNative static const JNINativeMethod gDrawMethods[] = { - {"nDrawColor","(JII)V", (void*) CanvasJNI::drawColor}, - {"nDrawColor","(JJJI)V", (void*) CanvasJNI::drawColorLong}, - {"nDrawPaint","(JJ)V", (void*) CanvasJNI::drawPaint}, - {"nDrawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint}, - {"nDrawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints}, - {"nDrawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine}, - {"nDrawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines}, - {"nDrawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect}, - {"nDrawRegion", "(JJJ)V", (void*) CanvasJNI::drawRegion }, - {"nDrawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect}, - {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*) CanvasJNI::drawDoubleRoundRectXY}, - {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*) CanvasJNI::drawDoubleRoundRectRadii}, - {"nDrawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle}, - {"nDrawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval}, - {"nDrawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc}, - {"nDrawPath","(JJJ)V", (void*) CanvasJNI::drawPath}, - {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices}, - {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch}, - {"nDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix}, - {"nDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh}, - {"nDrawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap}, - {"nDrawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect}, - {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray}, - {"nDrawGlyphs", "(J[I[FIIIJJ)V", (void*)CanvasJNI::drawGlyphs}, - {"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars}, - {"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString}, - {"nDrawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars}, - {"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString}, - {"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars}, - {"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString}, - {"nPunchHole", "(JFFFFFF)V", (void*) CanvasJNI::punchHole} -}; + {"nDrawColor", "(JII)V", (void*)CanvasJNI::drawColor}, + {"nDrawColor", "(JJJI)V", (void*)CanvasJNI::drawColorLong}, + {"nDrawPaint", "(JJ)V", (void*)CanvasJNI::drawPaint}, + {"nDrawPoint", "(JFFJ)V", (void*)CanvasJNI::drawPoint}, + {"nDrawPoints", "(J[FIIJ)V", (void*)CanvasJNI::drawPoints}, + {"nDrawLine", "(JFFFFJ)V", (void*)CanvasJNI::drawLine}, + {"nDrawLines", "(J[FIIJ)V", (void*)CanvasJNI::drawLines}, + {"nDrawRect", "(JFFFFJ)V", (void*)CanvasJNI::drawRect}, + {"nDrawRegion", "(JJJ)V", (void*)CanvasJNI::drawRegion}, + {"nDrawRoundRect", "(JFFFFFFJ)V", (void*)CanvasJNI::drawRoundRect}, + {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*)CanvasJNI::drawDoubleRoundRectXY}, + {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*)CanvasJNI::drawDoubleRoundRectRadii}, + {"nDrawCircle", "(JFFFJ)V", (void*)CanvasJNI::drawCircle}, + {"nDrawOval", "(JFFFFJ)V", (void*)CanvasJNI::drawOval}, + {"nDrawArc", "(JFFFFFFZJ)V", (void*)CanvasJNI::drawArc}, + {"nDrawPath", "(JJJ)V", (void*)CanvasJNI::drawPath}, + {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices}, + {"nDrawMesh", "(JJIJ)V", (void*)CanvasJNI::drawMesh}, + {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch}, + {"nDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix}, + {"nDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh}, + {"nDrawBitmap", "(JJFFJIII)V", (void*)CanvasJNI::drawBitmap}, + {"nDrawBitmap", "(JJFFFFFFFFJII)V", (void*)CanvasJNI::drawBitmapRect}, + {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray}, + {"nDrawGlyphs", "(J[I[FIIIJJ)V", (void*)CanvasJNI::drawGlyphs}, + {"nDrawText", "(J[CIIFFIJ)V", (void*)CanvasJNI::drawTextChars}, + {"nDrawText", "(JLjava/lang/String;IIFFIJ)V", (void*)CanvasJNI::drawTextString}, + {"nDrawTextRun", "(J[CIIIIFFZJJ)V", (void*)CanvasJNI::drawTextRunChars}, + {"nDrawTextRun", "(JLjava/lang/String;IIIIFFZJ)V", (void*)CanvasJNI::drawTextRunString}, + {"nDrawTextOnPath", "(J[CIIJFFIJ)V", (void*)CanvasJNI::drawTextOnPathChars}, + {"nDrawTextOnPath", "(JLjava/lang/String;JFFIJ)V", (void*)CanvasJNI::drawTextOnPathString}, + {"nPunchHole", "(JFFFFFFF)V", (void*)CanvasJNI::punchHole}}; int register_android_graphics_Canvas(JNIEnv* env) { int ret = 0; diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp new file mode 100644 index 000000000000..4886fdd7ac67 --- /dev/null +++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2010 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. + */ + +#undef LOG_TAG +#define LOG_TAG "HardwareBufferRenderer" +#define ATRACE_TAG ATRACE_TAG_VIEW + +#include <GraphicsJNI.h> +#include <RootRenderNode.h> +#include <TreeInfo.h> +#include <android-base/unique_fd.h> +#include <android/native_window.h> +#include <nativehelper/JNIPlatformHelp.h> +#include <renderthread/CanvasContext.h> +#include <renderthread/RenderProxy.h> +#include <renderthread/RenderThread.h> + +#include "HardwareBufferHelpers.h" +#include "JvmErrorReporter.h" + +namespace android { + +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; + +struct { + jclass clazz; + jmethodID invokeRenderCallback; +} gHardwareBufferRendererClassInfo; + +static RenderCallback createRenderCallback(JNIEnv* env, jobject releaseCallback) { + if (releaseCallback == nullptr) return nullptr; + + JavaVM* vm = nullptr; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); + auto globalCallbackRef = + std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(releaseCallback)); + return [globalCallbackRef](android::base::unique_fd&& fd, int status) { + GraphicsJNI::getJNIEnv()->CallStaticVoidMethod( + gHardwareBufferRendererClassInfo.clazz, + gHardwareBufferRendererClassInfo.invokeRenderCallback, globalCallbackRef->object(), + reinterpret_cast<jint>(fd.release()), reinterpret_cast<jint>(status)); + }; +} + +static long android_graphics_HardwareBufferRenderer_createRootNode(JNIEnv* env, jobject) { + auto* node = new RootRenderNode(std::make_unique<JvmErrorReporter>(env)); + node->incStrong(nullptr); + node->setName("RootRenderNode"); + return reinterpret_cast<jlong>(node); +} + +static void android_graphics_hardwareBufferRenderer_destroyRootNode(JNIEnv*, jobject, + jlong renderNodePtr) { + auto* node = reinterpret_cast<RootRenderNode*>(renderNodePtr); + node->destroy(); +} + +static long android_graphics_HardwareBufferRenderer_create(JNIEnv* env, jobject, jobject buffer, + jlong renderNodePtr) { + auto* hardwareBuffer = HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(env, buffer); + auto* rootRenderNode = reinterpret_cast<RootRenderNode*>(renderNodePtr); + ContextFactoryImpl factory(rootRenderNode); + auto* proxy = new RenderProxy(true, rootRenderNode, &factory); + proxy->setHardwareBuffer(hardwareBuffer); + return (jlong)proxy; +} + +static void HardwareBufferRenderer_destroy(jobject renderProxy) { + auto* proxy = reinterpret_cast<RenderProxy*>(renderProxy); + delete proxy; +} + +static SkMatrix createMatrixFromBufferTransform(SkScalar width, SkScalar height, int transform) { + auto matrix = SkMatrix(); + switch (transform) { + case ANATIVEWINDOW_TRANSFORM_ROTATE_90: + matrix.setRotate(90); + matrix.postTranslate(width, 0); + break; + case ANATIVEWINDOW_TRANSFORM_ROTATE_180: + matrix.setRotate(180); + matrix.postTranslate(width, height); + break; + case ANATIVEWINDOW_TRANSFORM_ROTATE_270: + matrix.setRotate(270); + matrix.postTranslate(0, width); + break; + default: + ALOGE("Invalid transform provided. Transform should be validated from" + "the java side. Leveraging identity transform as a fallback"); + [[fallthrough]]; + case ANATIVEWINDOW_TRANSFORM_IDENTITY: + break; + } + return matrix; +} + +static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jobject renderProxy, + jint transform, jint width, jint height, + jlong colorspacePtr, jobject consumer) { + auto* proxy = reinterpret_cast<RenderProxy*>(renderProxy); + auto skWidth = static_cast<SkScalar>(width); + auto skHeight = static_cast<SkScalar>(height); + auto matrix = createMatrixFromBufferTransform(skWidth, skHeight, transform); + auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr); + proxy->setHardwareBufferRenderParams( + HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer))); + return proxy->syncAndDrawFrame(); +} + +static void android_graphics_HardwareBufferRenderer_setLightGeometry(JNIEnv*, jobject, + jobject renderProxyPtr, + jfloat lightX, jfloat lightY, + jfloat lightZ, + jfloat lightRadius) { + auto* proxy = reinterpret_cast<RenderProxy*>(renderProxyPtr); + proxy->setLightGeometry((Vector3){lightX, lightY, lightZ}, lightRadius); +} + +static void android_graphics_HardwareBufferRenderer_setLightAlpha(JNIEnv* env, jobject, + jobject renderProxyPtr, + jfloat ambientShadowAlpha, + jfloat spotShadowAlpha) { + auto* proxy = reinterpret_cast<RenderProxy*>(renderProxyPtr); + proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha)); +} + +static jlong android_graphics_HardwareBufferRenderer_getFinalizer() { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&HardwareBufferRenderer_destroy)); +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +const char* const kClassPathName = "android/graphics/HardwareBufferRenderer"; + +static const JNINativeMethod gMethods[] = { + {"nCreateHardwareBufferRenderer", "(Landroid/hardware/HardwareBuffer;J)J", + (void*)android_graphics_HardwareBufferRenderer_create}, + {"nRender", "(JIIIJLjava/util/function/Consumer;)I", + (void*)android_graphics_HardwareBufferRenderer_render}, + {"nCreateRootRenderNode", "()J", + (void*)android_graphics_HardwareBufferRenderer_createRootNode}, + {"nSetLightGeometry", "(JFFFF)V", + (void*)android_graphics_HardwareBufferRenderer_setLightGeometry}, + {"nSetLightAlpha", "(JFF)V", (void*)android_graphics_HardwareBufferRenderer_setLightAlpha}, + {"nGetFinalizer", "()J", (void*)android_graphics_HardwareBufferRenderer_getFinalizer}, + {"nDestroyRootRenderNode", "(J)V", + (void*)android_graphics_hardwareBufferRenderer_destroyRootNode}}; + +int register_android_graphics_HardwareBufferRenderer(JNIEnv* env) { + jclass hardwareBufferRendererClazz = + FindClassOrDie(env, "android/graphics/HardwareBufferRenderer"); + gHardwareBufferRendererClassInfo.clazz = hardwareBufferRendererClazz; + gHardwareBufferRendererClassInfo.invokeRenderCallback = + GetStaticMethodIDOrDie(env, hardwareBufferRendererClazz, "invokeRenderCallback", + "(Ljava/util/function/Consumer;II)V"); + HardwareBufferHelpers::init(); + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} + +} // namespace android
\ No newline at end of file diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index c48448dffdd2..3f4d004ae8fa 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -23,8 +23,16 @@ #include <Picture.h> #include <Properties.h> #include <RootRenderNode.h> +#include <SkBitmap.h> +#include <SkColorSpace.h> +#include <SkData.h> +#include <SkImage.h> #include <SkImagePriv.h> +#include <SkPicture.h> +#include <SkPixmap.h> #include <SkSerialProcs.h> +#include <SkStream.h> +#include <SkTypeface.h> #include <dlfcn.h> #include <gui/TraceUtils.h> #include <inttypes.h> @@ -48,6 +56,7 @@ #include <atomic> #include <vector> +#include "JvmErrorReporter.h" #include "android_graphics_HardwareRendererObserver.h" namespace android { @@ -80,35 +89,17 @@ struct { jmethodID onFrameComplete; } gFrameCompleteCallback; -static JNIEnv* getenv(JavaVM* vm) { - JNIEnv* env; - if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm); - } - return env; -} +struct { + jmethodID onCopyFinished; + jmethodID getDestinationBitmap; +} gCopyRequest; typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface); ANW_fromSurface fromSurface; -class JvmErrorReporter : public ErrorHandler { -public: - JvmErrorReporter(JNIEnv* env) { - env->GetJavaVM(&mVm); - } - - virtual void onError(const std::string& message) override { - JNIEnv* env = getenv(mVm); - jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); - } -private: - JavaVM* mVm; -}; - class FrameCommitWrapper : public LightRefBase<FrameCommitWrapper> { public: explicit FrameCommitWrapper(JNIEnv* env, jobject jobject) { - env->GetJavaVM(&mVm); mObject = env->NewGlobalRef(jobject); LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref"); } @@ -118,19 +109,18 @@ public: void onFrameCommit(bool didProduceBuffer) { if (mObject) { ATRACE_FORMAT("frameCommit success=%d", didProduceBuffer); - getenv(mVm)->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, - didProduceBuffer); + GraphicsJNI::getJNIEnv()->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, + didProduceBuffer); releaseObject(); } } private: - JavaVM* mVm; jobject mObject; void releaseObject() { if (mObject) { - getenv(mVm)->DeleteGlobalRef(mObject); + GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject); mObject = nullptr; } } @@ -258,6 +248,16 @@ static void android_view_ThreadedRenderer_setIsHighEndGfx(JNIEnv* env, jobject c Properties::setIsHighEndGfx(jIsHighEndGfx); } +static void android_view_ThreadedRenderer_setIsLowRam(JNIEnv* env, jobject clazz, + jboolean isLowRam) { + Properties::setIsLowRam(isLowRam); +} + +static void android_view_ThreadedRenderer_setIsSystemOrPersistent(JNIEnv* env, jobject clazz, + jboolean isSystemOrPersistent) { + Properties::setIsSystemOrPersistent(isSystemOrPersistent); +} + static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz, jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) { @@ -420,26 +420,6 @@ static void android_view_ThreadedRenderer_forceDrawNextFrame(JNIEnv* env, jobjec proxy->forceDrawNextFrame(); } -class JGlobalRefHolder { -public: - JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {} - - virtual ~JGlobalRefHolder() { - getenv(mVm)->DeleteGlobalRef(mObject); - mObject = nullptr; - } - - jobject object() { return mObject; } - JavaVM* vm() { return mVm; } - -private: - JGlobalRefHolder(const JGlobalRefHolder&) = delete; - void operator=(const JGlobalRefHolder&) = delete; - - JavaVM* mVm; - jobject mObject; -}; - using TextureMap = std::unordered_map<uint32_t, sk_sp<SkImage>>; struct PictureCaptureState { @@ -451,7 +431,7 @@ struct PictureCaptureState { }; // TODO: This & Multi-SKP & Single-SKP should all be de-duped into -// a single "make a SkPicture serailizable-safe" utility somewhere +// a single "make a SkPicture serializable-safe" utility somewhere class PictureWrapper : public Picture { public: PictureWrapper(sk_sp<SkPicture>&& src, const std::shared_ptr<PictureCaptureState>& state) @@ -555,7 +535,7 @@ static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* auto pictureState = std::make_shared<PictureCaptureState>(); proxy->setPictureCapturedCallback([globalCallbackRef, pictureState](sk_sp<SkPicture>&& picture) { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); Picture* wrapper = new PictureWrapper{std::move(picture), pictureState}; env->CallStaticVoidMethod(gHardwareRenderer.clazz, gHardwareRenderer.invokePictureCapturedCallback, @@ -577,7 +557,7 @@ static void android_view_ThreadedRenderer_setASurfaceTransactionCallback( vm, env->NewGlobalRef(aSurfaceTransactionCallback)); proxy->setASurfaceTransactionCallback( [globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) -> bool { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); jboolean ret = env->CallBooleanMethod( globalCallbackRef->object(), gASurfaceTransactionCallback.onMergeTransaction, @@ -599,7 +579,7 @@ static void android_view_ThreadedRenderer_setPrepareSurfaceControlForWebviewCall auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback)); proxy->setPrepareSurfaceControlForWebviewCallback([globalCallbackRef]() { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); env->CallVoidMethod(globalCallbackRef->object(), gPrepareSurfaceControlForWebviewCallback.prepare); }); @@ -618,7 +598,7 @@ static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env, env->NewGlobalRef(frameCallback)); proxy->setFrameCallback([globalCallbackRef](int32_t syncResult, int64_t frameNr) -> std::function<void(bool)> { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); ScopedLocalRef<jobject> frameCommitCallback( env, env->CallObjectMethod( globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw, @@ -657,22 +637,48 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env, auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback)); proxy->setFrameCompleteCallback([globalCallbackRef]() { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); env->CallVoidMethod(globalCallbackRef->object(), gFrameCompleteCallback.onFrameComplete); }); } } -static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, - jobject clazz, jobject jsurface, jint left, jint top, - jint right, jint bottom, jlong bitmapPtr) { - SkBitmap bitmap; - bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); +class CopyRequestAdapter : public CopyRequest { +public: + CopyRequestAdapter(JavaVM* vm, jobject jCopyRequest, Rect srcRect) + : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {} + + virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override { + JNIEnv* env = GraphicsJNI::getJNIEnv(); + jlong bitmapPtr = env->CallLongMethod( + mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight); + SkBitmap bitmap; + bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); + return bitmap; + } + + virtual void onCopyFinished(CopyResult result) override { + JNIEnv* env = GraphicsJNI::getJNIEnv(); + env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished, + static_cast<jint>(result)); + } + +private: + JGlobalRefHolder mRefHolder; +}; + +static void android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, jobject clazz, + jobject jsurface, jint left, jint top, + jint right, jint bottom, + jobject jCopyRequest) { + JavaVM* vm = nullptr; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); + auto copyRequest = std::make_shared<CopyRequestAdapter>(vm, env->NewGlobalRef(jCopyRequest), + Rect(left, top, right, bottom)); ANativeWindow* window = fromSurface(env, jsurface); - jint result = RenderProxy::copySurfaceInto(window, left, top, right, bottom, &bitmap); + RenderProxy::copySurfaceInto(window, std::move(copyRequest)); ANativeWindow_release(window); - return result; } class ContextFactory : public IContextFactory { @@ -811,6 +817,11 @@ static void android_view_ThreadedRenderer_setRtAnimationsEnabled(JNIEnv* env, jo RenderProxy::setRtAnimationsEnabled(enabled); } +static void android_view_ThreadedRenderer_notifyCallbackPending(JNIEnv*, jclass, jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->notifyCallbackPending(); +} + // Plumbs the display density down to DeviceInfo. static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, jint densityDpi) { // Convert from dpi to density-independent pixels. @@ -818,17 +829,18 @@ static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, DeviceInfo::setDensity(density); } -static void android_view_ThreadedRenderer_initDisplayInfo(JNIEnv*, jclass, jint physicalWidth, - jint physicalHeight, jfloat refreshRate, - jint wideColorDataspace, - jlong appVsyncOffsetNanos, - jlong presentationDeadlineNanos) { +static void android_view_ThreadedRenderer_initDisplayInfo( + JNIEnv* env, jclass, jint physicalWidth, jint physicalHeight, jfloat refreshRate, + jint wideColorDataspace, jlong appVsyncOffsetNanos, jlong presentationDeadlineNanos, + jboolean supportFp16ForHdr, jboolean supportMixedColorSpaces) { DeviceInfo::setWidth(physicalWidth); DeviceInfo::setHeight(physicalHeight); DeviceInfo::setRefreshRate(refreshRate); DeviceInfo::setWideColorDataspace(static_cast<ADataSpace>(wideColorDataspace)); DeviceInfo::setAppVsyncOffsetNanos(appVsyncOffsetNanos); DeviceInfo::setPresentationDeadlineNanos(presentationDeadlineNanos); + DeviceInfo::setSupportFp16ForHdr(supportFp16ForHdr); + DeviceInfo::setSupportMixedColorSpaces(supportMixedColorSpaces); } static void android_view_ThreadedRenderer_setDrawingEnabled(JNIEnv*, jclass, jboolean enabled) { @@ -910,6 +922,9 @@ static const JNINativeMethod gMethods[] = { {"nSetColorMode", "(JI)V", (void*)android_view_ThreadedRenderer_setColorMode}, {"nSetSdrWhitePoint", "(JF)V", (void*)android_view_ThreadedRenderer_setSdrWhitePoint}, {"nSetIsHighEndGfx", "(Z)V", (void*)android_view_ThreadedRenderer_setIsHighEndGfx}, + {"nSetIsLowRam", "(Z)V", (void*)android_view_ThreadedRenderer_setIsLowRam}, + {"nSetIsSystemOrPersistent", "(Z)V", + (void*)android_view_ThreadedRenderer_setIsSystemOrPersistent}, {"nSyncAndDrawFrame", "(J[JI)I", (void*)android_view_ThreadedRenderer_syncAndDrawFrame}, {"nDestroy", "(JJ)V", (void*)android_view_ThreadedRenderer_destroy}, {"nRegisterAnimatingRenderNode", "(JJ)V", @@ -961,7 +976,8 @@ static const JNINativeMethod gMethods[] = { (void*)android_view_ThreadedRenderer_setFrameCompleteCallback}, {"nAddObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_addObserver}, {"nRemoveObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeObserver}, - {"nCopySurfaceInto", "(Landroid/view/Surface;IIIIJ)I", + {"nCopySurfaceInto", + "(Landroid/view/Surface;IIIILandroid/graphics/HardwareRenderer$CopyRequest;)V", (void*)android_view_ThreadedRenderer_copySurfaceInto}, {"nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;", (void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode}, @@ -974,7 +990,7 @@ static const JNINativeMethod gMethods[] = { {"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark}, {"nSetDisplayDensityDpi", "(I)V", (void*)android_view_ThreadedRenderer_setDisplayDensityDpi}, - {"nInitDisplayInfo", "(IIFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo}, + {"nInitDisplayInfo", "(IIFIJJZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo}, {"preload", "()V", (void*)android_view_ThreadedRenderer_preload}, {"isWebViewOverlaysEnabled", "()Z", (void*)android_view_ThreadedRenderer_isWebViewOverlaysEnabled}, @@ -982,6 +998,8 @@ static const JNINativeMethod gMethods[] = { {"nIsDrawingEnabled", "()Z", (void*)android_view_ThreadedRenderer_isDrawingEnabled}, {"nSetRtAnimationsEnabled", "(Z)V", (void*)android_view_ThreadedRenderer_setRtAnimationsEnabled}, + {"nNotifyCallbackPending", "(J)V", + (void*)android_view_ThreadedRenderer_notifyCallbackPending}, }; static JavaVM* mJvm = nullptr; @@ -1034,6 +1052,11 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) { gFrameCompleteCallback.onFrameComplete = GetMethodIDOrDie(env, frameCompleteClass, "onFrameComplete", "()V"); + jclass copyRequest = FindClassOrDie(env, "android/graphics/HardwareRenderer$CopyRequest"); + gCopyRequest.onCopyFinished = GetMethodIDOrDie(env, copyRequest, "onCopyFinished", "(I)V"); + gCopyRequest.getDestinationBitmap = + GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J"); + void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface"); LOG_ALWAYS_FATAL_IF(fromSurface == nullptr, diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index 09be630dc741..f17129c8d953 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -22,7 +22,10 @@ #include "SkFont.h" #include "SkFontMetrics.h" #include "SkFontMgr.h" +#include "SkRect.h" #include "SkRefCnt.h" +#include "SkScalar.h" +#include "SkStream.h" #include "SkTypeface.h" #include "GraphicsJNI.h" #include <nativehelper/ScopedUtfChars.h> @@ -225,7 +228,7 @@ static jlong Font_getReleaseNativeFontFunc(CRITICAL_JNI_PARAMS) { static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontPtr) { FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr); minikin::BufferReader reader = font->font->typefaceMetadataReader(); - if (reader.data() != nullptr) { + if (reader.current() != nullptr) { std::string path = std::string(reader.readString()); if (path.empty()) { return nullptr; @@ -267,7 +270,7 @@ static jint Font_getPackedStyle(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr); minikin::BufferReader reader = font->font->typefaceMetadataReader(); - if (reader.data() != nullptr) { + if (reader.current() != nullptr) { reader.skipString(); // fontPath return reader.read<int>(); } else { @@ -280,7 +283,7 @@ static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { static jint Font_getAxisCount(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr); minikin::BufferReader reader = font->font->typefaceMetadataReader(); - if (reader.data() != nullptr) { + if (reader.current() != nullptr) { reader.skipString(); // fontPath reader.skip<int>(); // fontIndex return reader.readArray<minikin::FontVariation>().second; @@ -295,7 +298,7 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr); minikin::BufferReader reader = font->font->typefaceMetadataReader(); minikin::FontVariation var; - if (reader.data() != nullptr) { + if (reader.current() != nullptr) { reader.skipString(); // fontPath reader.skip<int>(); // fontIndex var = reader.readArray<minikin::FontVariation>().first[index]; diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp index b68213549938..897c4d71c0d5 100644 --- a/libs/hwui/jni/fonts/FontFamily.cpp +++ b/libs/hwui/jni/fonts/FontFamily.cpp @@ -57,7 +57,8 @@ static void FontFamily_Builder_addFont(CRITICAL_JNI_PARAMS_COMMA jlong builderPt // Regular JNI static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, - jstring langTags, jint variant, jboolean isCustomFallback) { + jstring langTags, jint variant, jboolean isCustomFallback, + jboolean isDefaultFallback) { std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr)); uint32_t localeId; if (langTags == nullptr) { @@ -66,9 +67,9 @@ static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderP ScopedUtfChars str(env, langTags); localeId = minikin::registerLocaleList(str.c_str()); } - std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>( + std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create( localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts), - isCustomFallback); + isCustomFallback, isDefaultFallback); if (family->getCoverage().length() == 0) { // No coverage means minikin rejected given font for some reasons. jniThrowException(env, "java/lang/IllegalArgumentException", @@ -116,10 +117,10 @@ static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gFontFamilyBuilderMethods[] = { - { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder }, - { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont }, - { "nBuild", "(JLjava/lang/String;IZ)J", (void*) FontFamily_Builder_build }, - { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc }, + {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder}, + {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont}, + {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build}, + {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc}, }; static const JNINativeMethod gFontFamilyMethods[] = { diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h index 3f89c0712407..6a052dbb7cea 100644 --- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h +++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h @@ -19,6 +19,8 @@ #include "RenderNode.h" #include "SkiaDisplayList.h" +class SkRRect; + namespace android { namespace uirenderer { namespace skiapipeline { diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index dc72aead4873..a4960ea17c79 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -24,6 +24,7 @@ #include "SkClipStack.h" #include "SkRect.h" #include "SkM44.h" +#include "include/gpu/GpuTypes.h" // from Skia #include "utils/GLUtils.h" namespace android { @@ -92,7 +93,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { SkImageInfo surfaceInfo = canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height()); tmpSurface = - SkSurface::MakeRenderTarget(directContext, SkBudgeted::kYes, surfaceInfo); + SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo); tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT); GrGLFramebufferInfo fboInfo; diff --git a/libs/hwui/pipeline/skia/HolePunch.h b/libs/hwui/pipeline/skia/HolePunch.h index 92c6f7721a08..d0e1ca35049a 100644 --- a/libs/hwui/pipeline/skia/HolePunch.h +++ b/libs/hwui/pipeline/skia/HolePunch.h @@ -17,7 +17,6 @@ #pragma once #include <string> -#include "SkRRect.h" namespace android { namespace uirenderer { diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp index 3ba540921f64..99f54c19d2e5 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.cpp +++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp @@ -25,6 +25,7 @@ #include "SkColorFilter.h" #include "SkRuntimeEffect.h" #include "SkSurface.h" +#include "Tonemapper.h" #include "gl/GrGLTypes.h" #include "math/mat4.h" #include "system/graphics-base-v1.0.h" @@ -76,37 +77,6 @@ static bool shouldFilterRect(const SkMatrix& matrix, const SkRect& srcRect, cons isIntegerAligned(dstDevRect.y())); } -static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader, - const shaders::LinearEffect& linearEffect, - float maxDisplayLuminance, - float currentDisplayLuminanceNits, - float maxLuminance) { - auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect)); - auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString)); - if (!runtimeEffect) { - LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str()); - } - - SkRuntimeShaderBuilder effectBuilder(std::move(runtimeEffect)); - - effectBuilder.child("child") = std::move(shader); - - const auto uniforms = shaders::buildLinearEffectUniforms( - linearEffect, mat4(), maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance); - - for (const auto& uniform : uniforms) { - effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size()); - } - - return effectBuilder.makeShader(); -} - -static bool isHdrDataspace(ui::Dataspace dataspace) { - const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK; - - return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG; -} - static void adjustCropForYUV(uint32_t format, int bufferWidth, int bufferHeight, SkRect* cropRect) { // Chroma channels of YUV420 images are subsampled we may need to shrink the crop region by // a whole texel on each side. Since skia still adds its own 0.5 inset, we apply an @@ -215,31 +185,10 @@ bool LayerDrawable::DrawLayer(GrRecordingContext* context, sampling = SkSamplingOptions(SkFilterMode::kLinear); } - const auto sourceDataspace = static_cast<ui::Dataspace>( - ColorSpaceToADataSpace(layerImage->colorSpace(), layerImage->colorType())); - const SkImageInfo& imageInfo = canvas->imageInfo(); - const auto destinationDataspace = static_cast<ui::Dataspace>( - ColorSpaceToADataSpace(imageInfo.colorSpace(), imageInfo.colorType())); - - if (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace)) { - const auto effect = shaders::LinearEffect{ - .inputDataspace = sourceDataspace, - .outputDataspace = destinationDataspace, - .undoPremultipliedAlpha = layerImage->alphaType() == kPremul_SkAlphaType, - .fakeInputDataspace = destinationDataspace}; - auto shader = layerImage->makeShader(sampling, - SkMatrix::RectToRect(skiaSrcRect, skiaDestRect)); - constexpr float kMaxDisplayBrightess = 1000.f; - constexpr float kCurrentDisplayBrightness = 500.f; - shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess, - kCurrentDisplayBrightness, - layer->getMaxLuminanceNits()); - paint.setShader(shader); - canvas->drawRect(skiaDestRect, paint); - } else { - canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint, - constraint); - } + tonemapPaint(layerImage->imageInfo(), canvas->imageInfo(), layer->getMaxLuminanceNits(), + paint); + canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint, + constraint); canvas->restore(); // restore the original matrix diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 9e17b9e6d985..1a47db5c8ec2 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -15,7 +15,11 @@ */ #include "RenderNodeDrawable.h" +#include <SkPaint.h> #include <SkPaintFilterCanvas.h> +#include <SkPoint.h> +#include <SkRRect.h> +#include <SkRect.h> #include <gui/TraceUtils.h> #include "RenderNode.h" #include "SkiaDisplayList.h" @@ -197,6 +201,7 @@ protected: paint.setAlpha((uint8_t)paint.getAlpha() * mAlpha); return true; } + void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override { // We unroll the drawable using "this" canvas, so that draw calls contained inside will // get their alpha applied. The default SkPaintFilterCanvas::onDrawDrawable does not unroll. @@ -288,7 +293,7 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { // with the same canvas transformation + clip into the target // canvas then draw the layer on top if (renderNode->hasHolePunches()) { - TransformCanvas transformCanvas(canvas, SkBlendMode::kClear); + TransformCanvas transformCanvas(canvas, SkBlendMode::kDstOut); displayList->draw(&transformCanvas); } canvas->drawImageRect(snapshotImage, SkRect::Make(srcBounds), diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h index 6c390c3fce24..c7582e734009 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h @@ -18,6 +18,7 @@ #include "SkiaUtils.h" +#include <SkBlendMode.h> #include <SkCanvas.h> #include <SkDrawable.h> #include <SkMatrix.h> diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp index 7cfccb56382c..11977bd54c2c 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp @@ -19,8 +19,15 @@ #include "SkiaDisplayList.h" #include "LightingInfo.h" +#include <SkColor.h> +#include <SkMatrix.h> +#include <SkPath.h> #include <SkPathOps.h> +#include <SkPoint3.h> +#include <SkRect.h> +#include <SkScalar.h> #include <SkShadowUtils.h> +#include <include/private/SkShadowFlags.h> namespace android { namespace uirenderer { diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 90c4440c8339..a55de95035a7 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -16,6 +16,7 @@ #include "ShaderCache.h" #include <GrDirectContext.h> +#include <SkData.h> #include <gui/TraceUtils.h> #include <log/log.h> #include <openssl/sha.h> diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 3e0fd5164011..bc35fa5f9987 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -17,12 +17,15 @@ #pragma once #include <GrContextOptions.h> +#include <SkRefCnt.h> #include <cutils/compiler.h> #include <memory> #include <mutex> #include <string> #include <vector> +class SkData; + namespace android { class BlobCache; @@ -45,7 +48,7 @@ public: * and puts the ShaderCache into an initialized state, such that it is * able to insert and retrieve entries from the cache. If identity is * non-null and validation fails, the cache is initialized but contains - * no data. If size is less than zero, the cache is initilaized but + * no data. If size is less than zero, the cache is initialized but * contains no data. * * This should be called when HWUI pipeline is initialized. When not in diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index fcfc4f82abed..f0dc5eb4dd0e 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -146,6 +146,16 @@ bool SkiaDisplayList::prepareListAndChildren( } } + for (auto& lottie : mLotties) { + // If any animated image in the display list needs updated, then damage the node. + if (lottie->isDirty()) { + isDirty = true; + } + if (lottie->isRunning()) { + info.out.hasAnimations = true; + } + } + for (auto& [vectorDrawable, cachedMatrix] : mVectorDrawables) { // If any vector drawable in the display list needs update, damage the node. if (vectorDrawable->isDirty()) { diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index 2a677344b7b2..39217fcf1a56 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -22,6 +22,7 @@ #include "RenderNodeDrawable.h" #include "TreeInfo.h" #include "hwui/AnimatedImageDrawable.h" +#include "hwui/LottieDrawable.h" #include "utils/LinearAllocator.h" #include "utils/Pair.h" @@ -186,6 +187,8 @@ public: return mHasHolePunches; } + // TODO(b/257304231): create common base class for Lotties and AnimatedImages + std::vector<LottieDrawable*> mLotties; std::vector<AnimatedImageDrawable*> mAnimatedImages; DisplayListData mDisplayList; diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 8e350d5012a5..202a62cf320c 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -55,7 +55,9 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() { MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. - if (!isSurfaceReady() && mNativeWindow) { + if (mHardwareBuffer) { + mRenderThread.requireGlContext(); + } else if (!isSurfaceReady() && mNativeWindow) { setSurface(mNativeWindow.get(), mSwapBehavior); } @@ -67,17 +69,24 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { } Frame SkiaOpenGLPipeline::getFrame() { - LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, - "drawRenderNode called on a context with no surface!"); - return mEglManager.beginFrame(mEglSurface); + if (mHardwareBuffer) { + AHardwareBuffer_Desc description; + AHardwareBuffer_describe(mHardwareBuffer, &description); + return Frame(description.width, description.height, 0); + } else { + LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, + "drawRenderNode called on a context with no surface!"); + return mEglManager.beginFrame(mEglSurface); + } } IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) { - if (!isCapturingSkp()) { + const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, + const HardwareBufferRenderParams& bufferParams) { + if (!isCapturingSkp() && !mHardwareBuffer) { mEglManager.damageFrame(frame, dirty); } @@ -104,19 +113,31 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); - sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget( - mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType, - mSurfaceColorSpace, &props)); + sk_sp<SkSurface> surface; + SkMatrix preTransform; + if (mHardwareBuffer) { + surface = getBufferSkSurface(bufferParams); + preTransform = bufferParams.getTransform(); + } else { + surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT, + getSurfaceOrigin(), colorType, + mSurfaceColorSpace, &props); + preTransform = SkMatrix::I(); + } - LightingInfo::updateLighting(lightGeometry, lightInfo); + SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y); + LightGeometry localGeometry = lightGeometry; + localGeometry.center.x = lightCenter.fX; + localGeometry.center.y = lightCenter.fY; + LightingInfo::updateLighting(localGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, - SkMatrix::I()); + preTransform); // Draw visual debugging features if (CC_UNLIKELY(Properties::showDirtyRegions || ProfileType::None != Properties::getProfileType())) { SkCanvas* profileCanvas = surface->getCanvas(); - SkiaProfileRenderer profileRenderer(profileCanvas); + SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height()); profiler->draw(profileRenderer); } @@ -142,6 +163,10 @@ bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect // metrics the frame was swapped at this point currentFrameInfo->markSwapBuffers(); + if (mHardwareBuffer) { + return false; + } + *requireSwap = drew || mEglManager.damageRequiresSwap(); if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) { @@ -197,6 +222,26 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh return false; } +[[nodiscard]] android::base::unique_fd SkiaOpenGLPipeline::flush() { + int fence = -1; + EGLSyncKHR sync = EGL_NO_SYNC_KHR; + mEglManager.createReleaseFence(true, &sync, &fence); + // If a sync object is returned here then the device does not support native + // fences, we block on the returned sync and return -1 as a file descriptor + if (sync != EGL_NO_SYNC_KHR) { + EGLDisplay display = mEglManager.eglDisplay(); + EGLint result = eglClientWaitSyncKHR(display, sync, 0, 1000000000); + if (result == EGL_FALSE) { + ALOGE("EglManager::createReleaseFence: error waiting for previous fence: %#x", + eglGetError()); + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + ALOGE("EglManager::createReleaseFence: timeout waiting for previous fence"); + } + eglDestroySyncKHR(display, sync); + } + return android::base::unique_fd(fence); +} + bool SkiaOpenGLPipeline::isSurfaceReady() { return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index a80c613697f2..940d6bfdb83c 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -21,6 +21,7 @@ #include "SkiaPipeline.h" #include "renderstate/RenderState.h" +#include "renderthread/HardwareBufferRenderParams.h" namespace android { @@ -36,19 +37,18 @@ public: renderthread::MakeCurrentResult makeCurrent() override; renderthread::Frame getFrame() override; - renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame, - const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, - const LightInfo& lightInfo, - const std::vector<sp<RenderNode> >& renderNodes, - FrameInfoVisualizer* profiler) override; + renderthread::IRenderPipeline::DrawResult draw( + const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, + const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler, + const renderthread::HardwareBufferRenderParams& bufferParams) override; GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; } bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; + [[nodiscard]] android::base::unique_fd flush() override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index bc386feb2d6f..3692f0940b28 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -16,16 +16,27 @@ #include "SkiaPipeline.h" +#include <SkCanvas.h> +#include <SkColor.h> +#include <SkColorSpace.h> +#include <SkData.h> +#include <SkImage.h> #include <SkImageEncoder.h> #include <SkImageInfo.h> #include <SkImagePriv.h> +#include <SkMatrix.h> #include <SkMultiPictureDocument.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> #include <SkPicture.h> #include <SkPictureRecorder.h> +#include <SkRect.h> +#include <SkRefCnt.h> #include <SkSerialProcs.h> +#include <SkStream.h> +#include <SkString.h> #include <SkTypeface.h> +#include "include/gpu/GpuTypes.h" // from Skia #include <android-base/properties.h> #include <unistd.h> @@ -177,7 +188,7 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), - SkBudgeted::kYes, info, 0, + skgpu::Budgeted::kYes, info, 0, this->getSurfaceOrigin(), &props)); if (node->getLayerSurface()) { // update the transform in window of the layer to reset its origin wrt light source @@ -594,6 +605,31 @@ void SkiaPipeline::dumpResourceCacheUsage() const { ALOGD("%s", log.c_str()); } +void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) { + if (mHardwareBuffer) { + AHardwareBuffer_release(mHardwareBuffer); + mHardwareBuffer = nullptr; + } + + if (buffer) { + AHardwareBuffer_acquire(buffer); + mHardwareBuffer = buffer; + } +} + +sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface( + const renderthread::HardwareBufferRenderParams& bufferParams) { + auto bufferColorSpace = bufferParams.getColorSpace(); + if (mBufferSurface == nullptr || mBufferColorSpace == nullptr || + !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) { + mBufferSurface = SkSurface::MakeFromAHardwareBuffer( + mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin, + bufferColorSpace, nullptr, true); + mBufferColorSpace = bufferColorSpace; + } + return mBufferSurface; +} + void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { mColorMode = colorMode; switch (colorMode) { diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index bc8a5659dd83..4f9334654c9b 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -16,14 +16,18 @@ #pragma once -#include <SkSurface.h> +#include <SkColorSpace.h> #include <SkDocument.h> #include <SkMultiPictureDocument.h> +#include <SkSurface.h> + #include "Lighting.h" #include "hwui/AnimatedImageDrawable.h" #include "renderthread/CanvasContext.h" +#include "renderthread/HardwareBufferRenderParams.h" #include "renderthread/IRenderPipeline.h" +class SkFILEWStream; class SkPictureRecorder; struct SkSharingSerialContext; @@ -71,11 +75,20 @@ public: mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None; } + virtual void setHardwareBuffer(AHardwareBuffer* buffer) override; + bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; } + protected: + sk_sp<SkSurface> getBufferSkSurface( + const renderthread::HardwareBufferRenderParams& bufferParams); void dumpResourceCacheUsage() const; renderthread::RenderThread& mRenderThread; + AHardwareBuffer* mHardwareBuffer = nullptr; + sk_sp<SkSurface> mBufferSurface = nullptr; + sk_sp<SkColorSpace> mBufferColorSpace = nullptr; + ColorMode mColorMode = ColorMode::Default; SkColorType mSurfaceColorType; sk_sp<SkColorSpace> mSurfaceColorSpace; diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp index 492c39f1288c..81cfc5d93419 100644 --- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp +++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp @@ -33,13 +33,5 @@ void SkiaProfileRenderer::drawRects(const float* rects, int count, const SkPaint } } -uint32_t SkiaProfileRenderer::getViewportWidth() { - return mCanvas->imageInfo().width(); -} - -uint32_t SkiaProfileRenderer::getViewportHeight() { - return mCanvas->imageInfo().height(); -} - } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h index dc8420f4e01b..96d2a5e58139 100644 --- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h +++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h @@ -23,18 +23,21 @@ namespace uirenderer { class SkiaProfileRenderer : public IProfileRenderer { public: - explicit SkiaProfileRenderer(SkCanvas* canvas) : mCanvas(canvas) {} + explicit SkiaProfileRenderer(SkCanvas* canvas, uint32_t width, uint32_t height) + : mCanvas(canvas), mWidth(width), mHeight(height) {} void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override; void drawRects(const float* rects, int count, const SkPaint& paint) override; - uint32_t getViewportWidth() override; - uint32_t getViewportHeight() override; + uint32_t getViewportWidth() override { return mWidth; } + uint32_t getViewportHeight() override { return mHeight; } virtual ~SkiaProfileRenderer() {} private: // Does not have ownership. SkCanvas* mCanvas; + uint32_t mWidth; + uint32_t mHeight; }; } /* namespace uirenderer */ diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 9c51e628e04a..db449d608c1f 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -16,7 +16,20 @@ #include "SkiaRecordingCanvas.h" #include "hwui/Paint.h" +#include <include/private/SkTemplates.h> // SkAutoSTMalloc +#include <SkBlendMode.h> +#include <SkData.h> +#include <SkDrawable.h> +#include <SkImage.h> #include <SkImagePriv.h> +#include <SkMatrix.h> +#include <SkPaint.h> +#include <SkPoint.h> +#include <SkRect.h> +#include <SkRefCnt.h> +#include <SkRRect.h> +#include <SkSamplingOptions.h> +#include <SkTypes.h> #include "CanvasTransform.h" #ifdef __ANDROID__ // Layoutlib does not support Layers #include "Layer.h" @@ -56,20 +69,22 @@ void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, in mDisplayList->setHasHolePunches(false); } -void SkiaRecordingCanvas::punchHole(const SkRRect& rect) { - // Add the marker annotation to allow HWUI to determine where the current - // clip/transformation should be applied +void SkiaRecordingCanvas::punchHole(const SkRRect& rect, float alpha) { + // Add the marker annotation to allow HWUI to determine the current + // clip/transformation and alpha should be applied SkVector vector = rect.getSimpleRadii(); - float data[2]; + float data[3]; data[0] = vector.x(); data[1] = vector.y(); + data[2] = alpha; mRecorder.drawAnnotation(rect.rect(), HOLE_PUNCH_ANNOTATION.c_str(), - SkData::MakeWithCopy(data, 2 * sizeof(float))); + SkData::MakeWithCopy(data, sizeof(data))); // Clear the current rect within the layer itself SkPaint paint = SkPaint(); - paint.setColor(0); - paint.setBlendMode(SkBlendMode::kClear); + paint.setColor(SkColors::kBlack); + paint.setAlphaf(alpha); + paint.setBlendMode(SkBlendMode::kDstOut); mRecorder.drawRRect(rect, paint); mDisplayList->setHasHolePunches(true); @@ -173,6 +188,11 @@ void SkiaRecordingCanvas::drawWebViewFunctor(int functor) { #endif } +void SkiaRecordingCanvas::drawLottie(LottieDrawable* lottie) { + drawDrawable(lottie); + mDisplayList->mLotties.push_back(lottie); +} + void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { mRecorder.drawVectorDrawable(tree); SkMatrix mat; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 1445a27e4248..c823d8d0a755 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -22,6 +22,11 @@ #include "SkiaDisplayList.h" #include "pipeline/skia/AnimatedDrawables.h" +class SkBitmap; +class SkMatrix; +class SkPaint; +class SkRRect; + namespace android { namespace uirenderer { namespace skiapipeline { @@ -45,7 +50,7 @@ public: initDisplayList(renderNode, width, height); } - virtual void punchHole(const SkRRect& rect) override; + virtual void punchHole(const SkRRect& rect, float alpha) override; virtual void finishRecording(uirenderer::RenderNode* destination) override; std::unique_ptr<SkiaDisplayList> finishRecording(); @@ -73,6 +78,7 @@ public: uirenderer::CanvasPropertyPaint* paint) override; virtual void drawRipple(const RippleDrawableParams& params) override; + virtual void drawLottie(LottieDrawable* lottieDrawable) override; virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; virtual void enableZ(bool enableZ) override; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index cc2565d88d5e..b94b6cf0546a 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -57,43 +57,63 @@ VulkanManager& SkiaVulkanPipeline::vulkanManager() { MakeCurrentResult SkiaVulkanPipeline::makeCurrent() { // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. - if (!isSurfaceReady() && mNativeWindow) { + if (mHardwareBuffer) { + mRenderThread.requireVkContext(); + } else if (!isSurfaceReady() && mNativeWindow) { setSurface(mNativeWindow.get(), SwapBehavior::kSwap_default); } return isContextReady() ? MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed; } Frame SkiaVulkanPipeline::getFrame() { - LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!"); - return vulkanManager().dequeueNextBuffer(mVkSurface); + if (mHardwareBuffer) { + AHardwareBuffer_Desc description; + AHardwareBuffer_describe(mHardwareBuffer, &description); + return Frame(description.width, description.height, 0); + } else { + LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, + "getFrame() called on a context with no surface!"); + return vulkanManager().dequeueNextBuffer(mVkSurface); + } } IRenderPipeline::DrawResult SkiaVulkanPipeline::draw( const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) { - sk_sp<SkSurface> backBuffer = mVkSurface->getCurrentSkSurface(); + const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, + const HardwareBufferRenderParams& bufferParams) { + sk_sp<SkSurface> backBuffer; + SkMatrix preTransform; + if (mHardwareBuffer) { + backBuffer = getBufferSkSurface(bufferParams); + preTransform = bufferParams.getTransform(); + } else { + backBuffer = mVkSurface->getCurrentSkSurface(); + preTransform = mVkSurface->getCurrentPreTransform(); + } + if (backBuffer.get() == nullptr) { return {false, -1}; } // update the coordinates of the global light position based on surface rotation - SkPoint lightCenter = mVkSurface->getCurrentPreTransform().mapXY(lightGeometry.center.x, - lightGeometry.center.y); + SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y); LightGeometry localGeometry = lightGeometry; localGeometry.center.x = lightCenter.fX; localGeometry.center.y = lightCenter.fY; LightingInfo::updateLighting(localGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer, - mVkSurface->getCurrentPreTransform()); + preTransform); // Draw visual debugging features if (CC_UNLIKELY(Properties::showDirtyRegions || ProfileType::None != Properties::getProfileType())) { SkCanvas* profileCanvas = backBuffer->getCanvas(); - SkiaProfileRenderer profileRenderer(profileCanvas); + SkAutoCanvasRestore saver(profileCanvas, true); + profileCanvas->concat(mVkSurface->getCurrentPreTransform()); + SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height()); profiler->draw(profileRenderer); } @@ -114,12 +134,16 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw( bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) { - *requireSwap = drew; - // Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point currentFrameInfo->markSwapBuffers(); + if (mHardwareBuffer) { + return false; + } + + *requireSwap = drew; + if (*requireSwap) { vulkanManager().swapBuffers(mVkSurface, screenDirty); } @@ -135,6 +159,12 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} +[[nodiscard]] android::base::unique_fd SkiaVulkanPipeline::flush() { + int fence = -1; + vulkanManager().createReleaseFence(&fence, mRenderThread.getGrContext()); + return android::base::unique_fd(fence); +} + // We can safely ignore the swap behavior because VkManager will always operate // in a mode equivalent to EGLManager::SwapBehavior::kBufferAge bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapBehavior*/) { diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index a6e685d08aeb..2c7b268e8174 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -16,11 +16,15 @@ #pragma once +#include "SkRefCnt.h" #include "SkiaPipeline.h" +#include "renderstate/RenderState.h" +#include "renderthread/HardwareBufferRenderParams.h" #include "renderthread/VulkanManager.h" #include "renderthread/VulkanSurface.h" -#include "renderstate/RenderState.h" +class SkBitmap; +struct SkRect; namespace android { namespace uirenderer { @@ -33,18 +37,18 @@ public: renderthread::MakeCurrentResult makeCurrent() override; renderthread::Frame getFrame() override; - renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame, - const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, - const LightInfo& lightInfo, - const std::vector<sp<RenderNode> >& renderNodes, - FrameInfoVisualizer* profiler) override; + renderthread::IRenderPipeline::DrawResult draw( + const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, + const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler, + const renderthread::HardwareBufferRenderParams& bufferParams) override; GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; } bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; + [[nodiscard]] android::base::unique_fd flush() override; + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; void onStop() override; bool isSurfaceReady() override; @@ -59,7 +63,6 @@ protected: private: renderthread::VulkanManager& vulkanManager(); - renderthread::VulkanSurface* mVkSurface = nullptr; sp<ANativeWindow> mNativeWindow; }; diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp index 2dbeb3adfab3..cad3703d8d2b 100644 --- a/libs/hwui/pipeline/skia/StretchMask.cpp +++ b/libs/hwui/pipeline/skia/StretchMask.cpp @@ -14,8 +14,12 @@ * limitations under the License. */ #include "StretchMask.h" -#include "SkSurface.h" + +#include "SkBlendMode.h" #include "SkCanvas.h" +#include "SkSurface.h" +#include "include/gpu/GpuTypes.h" // from Skia + #include "TransformCanvas.h" #include "SkiaDisplayList.h" @@ -34,7 +38,7 @@ void StretchMask::draw(GrRecordingContext* context, // not match. mMaskSurface = SkSurface::MakeRenderTarget( context, - SkBudgeted::kYes, + skgpu::Budgeted::kYes, SkImageInfo::Make( width, height, diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp index 41e36874b862..c320df035d08 100644 --- a/libs/hwui/pipeline/skia/TransformCanvas.cpp +++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp @@ -19,19 +19,25 @@ #include "HolePunch.h" #include "SkData.h" #include "SkDrawable.h" +#include "SkMatrix.h" +#include "SkPaint.h" +#include "SkRect.h" +#include "SkRRect.h" using namespace android::uirenderer::skiapipeline; void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkData* value) { if (HOLE_PUNCH_ANNOTATION == key) { auto* rectParams = reinterpret_cast<const float*>(value->data()); - float radiusX = rectParams[0]; - float radiusY = rectParams[1]; + const float radiusX = rectParams[0]; + const float radiusY = rectParams[1]; + const float alpha = rectParams[2]; SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY); SkPaint paint; paint.setColor(SkColors::kBlack); paint.setBlendMode(mHolePunchBlendMode); + paint.setAlphaf(alpha); mWrappedCanvas->drawRRect(roundRect, paint); } } diff --git a/libs/hwui/pipeline/skia/TransformCanvas.h b/libs/hwui/pipeline/skia/TransformCanvas.h index 685b71d017e9..15f0c1abc55a 100644 --- a/libs/hwui/pipeline/skia/TransformCanvas.h +++ b/libs/hwui/pipeline/skia/TransformCanvas.h @@ -19,6 +19,13 @@ #include "SkPaintFilterCanvas.h" #include <effects/StretchEffect.h> +class SkData; +class SkDrawable; +class SkMatrix; +class SkPaint; +enum class SkBlendMode; +struct SkRect; + class TransformCanvas : public SkPaintFilterCanvas { public: TransformCanvas(SkCanvas* target, SkBlendMode blendmode) : diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index 3c7617d35c7c..e168a7b9459a 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -33,6 +33,8 @@ #include "thread/ThreadBase.h" #include "utils/TimeUtils.h" +#include <SkBlendMode.h> + namespace android { namespace uirenderer { namespace skiapipeline { diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index ded2b06fb3cf..1c7688464c27 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -16,6 +16,16 @@ #include "CacheManager.h" +#include <GrContextOptions.h> +#include <SkExecutor.h> +#include <SkGraphics.h> +#include <SkMathPriv.h> +#include <math.h> +#include <utils/Trace.h> + +#include <set> + +#include "CanvasContext.h" #include "DeviceInfo.h" #include "Layer.h" #include "Properties.h" @@ -25,40 +35,34 @@ #include "pipeline/skia/SkiaMemoryTracer.h" #include "renderstate/RenderState.h" #include "thread/CommonPool.h" -#include <utils/Trace.h> - -#include <GrContextOptions.h> -#include <SkExecutor.h> -#include <SkGraphics.h> -#include <SkMathPriv.h> -#include <math.h> -#include <set> namespace android { namespace uirenderer { namespace renderthread { -// This multiplier was selected based on historical review of cache sizes relative -// to the screen resolution. This is meant to be a conservative default based on -// that analysis. The 4.0f is used because the default pixel format is assumed to -// be ARGB_8888. -#define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f) -#define BACKGROUND_RETENTION_PERCENTAGE (0.5f) - -CacheManager::CacheManager() - : mMaxSurfaceArea(DeviceInfo::getWidth() * DeviceInfo::getHeight()) - , mMaxResourceBytes(mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER) - , mBackgroundResourceBytes(mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE) - // This sets the maximum size for a single texture atlas in the GPU font cache. If - // necessary, the cache can allocate additional textures that are counted against the - // total cache limits provided to Skia. - , mMaxGpuFontAtlasBytes(GrNextSizePow2(mMaxSurfaceArea)) - // This sets the maximum size of the CPU font cache to be at least the same size as the - // total number of GPU font caches (i.e. 4 separate GPU atlases). - , mMaxCpuFontCacheBytes( - std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit())) - , mBackgroundCpuFontCacheBytes(mMaxCpuFontCacheBytes * BACKGROUND_RETENTION_PERCENTAGE) { +CacheManager::CacheManager(RenderThread& thread) + : mRenderThread(thread), mMemoryPolicy(loadMemoryPolicy()) { + mMaxSurfaceArea = static_cast<size_t>((DeviceInfo::getWidth() * DeviceInfo::getHeight()) * + mMemoryPolicy.initialMaxSurfaceAreaScale); + setupCacheLimits(); +} + +void CacheManager::setupCacheLimits() { + mMaxResourceBytes = mMaxSurfaceArea * mMemoryPolicy.surfaceSizeMultiplier; + mBackgroundResourceBytes = mMaxResourceBytes * mMemoryPolicy.backgroundRetentionPercent; + // This sets the maximum size for a single texture atlas in the GPU font cache. If + // necessary, the cache can allocate additional textures that are counted against the + // total cache limits provided to Skia. + mMaxGpuFontAtlasBytes = GrNextSizePow2(mMaxSurfaceArea); + // This sets the maximum size of the CPU font cache to be at least the same size as the + // total number of GPU font caches (i.e. 4 separate GPU atlases). + mMaxCpuFontCacheBytes = std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit()); + mBackgroundCpuFontCacheBytes = mMaxCpuFontCacheBytes * mMemoryPolicy.backgroundRetentionPercent; + SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); + if (mGrContext) { + mGrContext->setResourceCacheLimit(mMaxResourceBytes); + } } void CacheManager::reset(sk_sp<GrDirectContext> context) { @@ -69,6 +73,7 @@ void CacheManager::reset(sk_sp<GrDirectContext> context) { if (context) { mGrContext = std::move(context); mGrContext->setResourceCacheLimit(mMaxResourceBytes); + mLastDeferredCleanup = systemTime(CLOCK_MONOTONIC); } } @@ -93,10 +98,9 @@ void CacheManager::configureContext(GrContextOptions* contextOptions, const void auto& cache = skiapipeline::ShaderCache::get(); cache.initShaderDiskCache(identity, size); contextOptions->fPersistentCache = &cache; - contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting; } -void CacheManager::trimMemory(TrimMemoryMode mode) { +void CacheManager::trimMemory(TrimLevel mode) { if (!mGrContext) { return; } @@ -104,21 +108,28 @@ void CacheManager::trimMemory(TrimMemoryMode mode) { // flush and submit all work to the gpu and wait for it to finish mGrContext->flushAndSubmit(/*syncCpu=*/true); + if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) { + mode = TrimLevel::COMPLETE; + } + switch (mode) { - case TrimMemoryMode::Complete: + case TrimLevel::COMPLETE: mGrContext->freeGpuResources(); SkGraphics::PurgeAllCaches(); + mRenderThread.destroyRenderingContext(); break; - case TrimMemoryMode::UiHidden: + case TrimLevel::UI_HIDDEN: // Here we purge all the unlocked scratch resources and then toggle the resources cache // limits between the background and max amounts. This causes the unlocked resources // that have persistent data to be purged in LRU order. - mGrContext->purgeUnlockedResources(true); mGrContext->setResourceCacheLimit(mBackgroundResourceBytes); - mGrContext->setResourceCacheLimit(mMaxResourceBytes); SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes); + mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly); + mGrContext->setResourceCacheLimit(mMaxResourceBytes); SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); break; + default: + break; } } @@ -147,11 +158,29 @@ void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) { } void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) { + log.appendFormat(R"(Memory policy: + Max surface area: %zu + Max resource usage: %.2fMB (x%.0f) + Background retention: %.0f%% (altUiHidden = %s) +)", + mMaxSurfaceArea, mMaxResourceBytes / 1000000.f, + mMemoryPolicy.surfaceSizeMultiplier, + mMemoryPolicy.backgroundRetentionPercent * 100.0f, + mMemoryPolicy.useAlternativeUiHidden ? "true" : "false"); + if (Properties::isSystemOrPersistent) { + log.appendFormat(" IsSystemOrPersistent\n"); + } + log.appendFormat(" GPU Context timeout: %" PRIu64 "\n", ns2s(mMemoryPolicy.contextTimeout)); + size_t stoppedContexts = 0; + for (auto context : mCanvasContexts) { + if (context->isStopped()) stoppedContexts++; + } + log.appendFormat("Contexts: %zu (stopped = %zu)\n", mCanvasContexts.size(), stoppedContexts); + if (!mGrContext) { - log.appendFormat("No valid cache instance.\n"); + log.appendFormat("No GPU context.\n"); return; } - std::vector<skiapipeline::ResourcePair> cpuResourceMap = { {"skia/sk_resource_cache/bitmap_", "Bitmaps"}, {"skia/sk_resource_cache/rrect-blur_", "Masks"}, @@ -199,6 +228,8 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) } void CacheManager::onFrameCompleted() { + cancelDestroyContext(); + mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC); if (ATRACE_ENABLED()) { static skiapipeline::ATraceMemoryDump tracer; tracer.startFrame(); @@ -210,11 +241,82 @@ void CacheManager::onFrameCompleted() { } } -void CacheManager::performDeferredCleanup(nsecs_t cleanupOlderThanMillis) { - if (mGrContext) { - mGrContext->performDeferredCleanup( - std::chrono::milliseconds(cleanupOlderThanMillis), - /* scratchResourcesOnly */true); +void CacheManager::onThreadIdle() { + if (!mGrContext || mFrameCompletions.size() == 0) return; + + const nsecs_t now = systemTime(CLOCK_MONOTONIC); + // Rate limiting + if ((now - mLastDeferredCleanup) < 25_ms) { + mLastDeferredCleanup = now; + const nsecs_t frameCompleteNanos = mFrameCompletions[0]; + const nsecs_t frameDiffNanos = now - frameCompleteNanos; + const nsecs_t cleanupMillis = + ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention)); + mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis), + mMemoryPolicy.purgeScratchOnly); + } +} + +void CacheManager::scheduleDestroyContext() { + if (mMemoryPolicy.contextTimeout > 0) { + mRenderThread.queue().postDelayed(mMemoryPolicy.contextTimeout, + [this, genId = mGenerationId] { + if (mGenerationId != genId) return; + // GenID should have already stopped this, but just in + // case + if (!areAllContextsStopped()) return; + mRenderThread.destroyRenderingContext(); + }); + } +} + +void CacheManager::cancelDestroyContext() { + if (mIsDestructionPending) { + mIsDestructionPending = false; + mGenerationId++; + } +} + +bool CacheManager::areAllContextsStopped() { + for (auto context : mCanvasContexts) { + if (!context->isStopped()) return false; + } + return true; +} + +void CacheManager::checkUiHidden() { + if (!mGrContext) return; + + if (mMemoryPolicy.useAlternativeUiHidden && areAllContextsStopped()) { + trimMemory(TrimLevel::UI_HIDDEN); + } +} + +void CacheManager::registerCanvasContext(CanvasContext* context) { + mCanvasContexts.push_back(context); + cancelDestroyContext(); +} + +void CacheManager::unregisterCanvasContext(CanvasContext* context) { + std::erase(mCanvasContexts, context); + checkUiHidden(); + if (mCanvasContexts.empty()) { + scheduleDestroyContext(); + } +} + +void CacheManager::onContextStopped(CanvasContext* context) { + checkUiHidden(); + if (mMemoryPolicy.releaseContextOnStoppedOnly && areAllContextsStopped()) { + scheduleDestroyContext(); + } +} + +void CacheManager::notifyNextFrameSize(int width, int height) { + int frameArea = width * height; + if (frameArea > mMaxSurfaceArea) { + mMaxSurfaceArea = frameArea; + setupCacheLimits(); } } diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index af82672c6f23..d21ac9badc43 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -22,7 +22,11 @@ #endif #include <SkSurface.h> #include <utils/String8.h> + #include <vector> + +#include "MemoryPolicy.h" +#include "utils/RingBuffer.h" #include "utils/TimeUtils.h" namespace android { @@ -35,17 +39,15 @@ class RenderState; namespace renderthread { -class IRenderPipeline; class RenderThread; +class CanvasContext; class CacheManager { public: - enum class TrimMemoryMode { Complete, UiHidden }; - #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration void configureContext(GrContextOptions* context, const void* identity, ssize_t size); #endif - void trimMemory(TrimMemoryMode mode); + void trimMemory(TrimLevel mode); void trimStaleResources(); void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr); void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage); @@ -53,30 +55,50 @@ public: size_t getCacheSize() const { return mMaxResourceBytes; } size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; } void onFrameCompleted(); + void notifyNextFrameSize(int width, int height); + + void onThreadIdle(); - void performDeferredCleanup(nsecs_t cleanupOlderThanMillis); + void registerCanvasContext(CanvasContext* context); + void unregisterCanvasContext(CanvasContext* context); + void onContextStopped(CanvasContext* context); private: friend class RenderThread; - explicit CacheManager(); + explicit CacheManager(RenderThread& thread); + void setupCacheLimits(); + bool areAllContextsStopped(); + void checkUiHidden(); + void scheduleDestroyContext(); + void cancelDestroyContext(); #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration void reset(sk_sp<GrDirectContext> grContext); #endif void destroy(); - const size_t mMaxSurfaceArea; + RenderThread& mRenderThread; + const MemoryPolicy& mMemoryPolicy; #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration sk_sp<GrDirectContext> mGrContext; #endif - const size_t mMaxResourceBytes; - const size_t mBackgroundResourceBytes; + size_t mMaxSurfaceArea = 0; + + size_t mMaxResourceBytes = 0; + size_t mBackgroundResourceBytes = 0; + + size_t mMaxGpuFontAtlasBytes = 0; + size_t mMaxCpuFontCacheBytes = 0; + size_t mBackgroundCpuFontCacheBytes = 0; + + std::vector<CanvasContext*> mCanvasContexts; + RingBuffer<uint64_t, 100> mFrameCompletions; - const size_t mMaxGpuFontAtlasBytes; - const size_t mMaxCpuFontCacheBytes; - const size_t mBackgroundCpuFontCacheBytes; + nsecs_t mLastDeferredCleanup = 0; + bool mIsDestructionPending = false; + uint32_t mGenerationId = 0; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 75d3ff7753cb..b769f8d15044 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -42,9 +42,6 @@ #include "utils/GLUtils.h" #include "utils/TimeUtils.h" -#define TRIM_MEMORY_COMPLETE 80 -#define TRIM_MEMORY_UI_HIDDEN 20 - #define LOG_FRAMETIME_MMA 0 #if LOG_FRAMETIME_MMA @@ -74,16 +71,19 @@ CanvasContext* ScopedActiveContext::sActiveContext = nullptr; } /* namespace */ CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent, - RenderNode* rootRenderNode, IContextFactory* contextFactory) { + RenderNode* rootRenderNode, IContextFactory* contextFactory, + int32_t uiThreadId, int32_t renderThreadId) { auto renderType = Properties::getRenderPipelineType(); switch (renderType) { case RenderPipelineType::SkiaGL: return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, - std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread)); + std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread), + uiThreadId, renderThreadId); case RenderPipelineType::SkiaVulkan: return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, - std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread)); + std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread), + uiThreadId, renderThreadId); default: LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType); break; @@ -113,7 +113,8 @@ void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory, - std::unique_ptr<IRenderPipeline> renderPipeline) + std::unique_ptr<IRenderPipeline> renderPipeline, pid_t uiThreadId, + pid_t renderThreadId) : mRenderThread(thread) , mGenerationID(0) , mOpaque(!translucent) @@ -121,7 +122,9 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* , mJankTracker(&thread.globalProfileData()) , mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos()) , mContentDrawBounds(0, 0, 0, 0) - , mRenderPipeline(std::move(renderPipeline)) { + , mRenderPipeline(std::move(renderPipeline)) + , mHintSessionWrapper(uiThreadId, renderThreadId) { + mRenderThread.cacheManager().registerCanvasContext(this); rootRenderNode->makeRoot(); mRenderNodes.emplace_back(rootRenderNode); mProfiler.setDensity(DeviceInfo::getDensity()); @@ -133,6 +136,7 @@ CanvasContext::~CanvasContext() { node->clearRoot(); } mRenderNodes.clear(); + mRenderThread.cacheManager().unregisterCanvasContext(this); } void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) { @@ -149,11 +153,13 @@ void CanvasContext::removeRenderNode(RenderNode* node) { void CanvasContext::destroy() { stopDrawing(); + setHardwareBuffer(nullptr); setSurface(nullptr); setSurfaceControl(nullptr); freePrefetchedLayers(); destroyHardwareResources(); mAnimationContext->destroy(); + mRenderThread.cacheManager().onContextStopped(this); } static void setBufferCount(ANativeWindow* window) { @@ -171,6 +177,19 @@ static void setBufferCount(ANativeWindow* window) { native_window_set_buffer_count(window, bufferCount); } +void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) { + if (mHardwareBuffer) { + AHardwareBuffer_release(mHardwareBuffer); + mHardwareBuffer = nullptr; + } + + if (buffer) { + AHardwareBuffer_acquire(buffer); + mHardwareBuffer = buffer; + } + mRenderPipeline->setHardwareBuffer(mHardwareBuffer); +} + void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) { ATRACE_CALL(); @@ -251,7 +270,8 @@ void CanvasContext::setStopped(bool stopped) { mGenerationID++; mRenderThread.removeFrameCallback(this); mRenderPipeline->onStop(); - } else if (mIsDirty && hasSurface()) { + mRenderThread.cacheManager().onContextStopped(this); + } else if (mIsDirty && hasOutputTarget()) { mRenderThread.postFrameCallback(this); } } @@ -384,7 +404,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mIsDirty = true; - if (CC_UNLIKELY(!hasSurface())) { + if (CC_UNLIKELY(!hasOutputTarget())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); info.out.canDrawThisFrame = false; return; @@ -461,7 +481,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy } void CanvasContext::stopDrawing() { - cleanupResources(); mRenderThread.removeFrameCallback(this); mAnimationContext->pauseAnimators(); mGenerationID++; @@ -470,18 +489,25 @@ void CanvasContext::stopDrawing() { void CanvasContext::notifyFramePending() { ATRACE_CALL(); mRenderThread.pushBackFrameCallback(this); + sendLoadResetHint(); } -nsecs_t CanvasContext::draw() { +void CanvasContext::draw() { if (auto grContext = getGrContext()) { if (grContext->abandoned()) { LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw"); - return 0; + return; } } SkRect dirty; mDamageAccumulator.finish(&dirty); + // reset syncDelayDuration each time we draw + nsecs_t syncDelayDuration = mSyncDelayDuration; + nsecs_t idleDuration = mIdleDuration; + mSyncDelayDuration = 0; + mIdleDuration = 0; + if (!Properties::isDrawingEnabled() || (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); @@ -498,7 +524,7 @@ nsecs_t CanvasContext::draw() { std::invoke(func, false /* didProduceBuffer */); } mFrameCommitCallbacks.clear(); - return 0; + return; } ScopedActiveContext activeContext(this); @@ -523,7 +549,7 @@ nsecs_t CanvasContext::draw() { std::scoped_lock lock(mFrameMetricsReporterMutex); drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds, mOpaque, - mLightInfo, mRenderNodes, &(profiler())); + mLightInfo, mRenderNodes, &(profiler()), mBufferParams); } uint64_t frameCompleteNr = getFrameNumber(); @@ -543,6 +569,8 @@ nsecs_t CanvasContext::draw() { } bool requireSwap = false; + bool didDraw = false; + int error = OK; bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult.success, windowDirty, mCurrentFrameInfo, &requireSwap); @@ -553,7 +581,7 @@ nsecs_t CanvasContext::draw() { mIsDirty = false; if (requireSwap) { - bool didDraw = true; + didDraw = true; // Handle any swapchain errors error = mNativeSurface->getAndClearError(); if (error == TIMED_OUT) { @@ -648,23 +676,25 @@ nsecs_t CanvasContext::draw() { } } - cleanupResources(); - mRenderThread.cacheManager().onFrameCompleted(); - return mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration); -} + int64_t intendedVsync = mCurrentFrameInfo->get(FrameInfoIndex::IntendedVsync); + int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline); + int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration); + + mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync); -void CanvasContext::cleanupResources() { - auto& tracker = mJankTracker.frames(); - auto size = tracker.size(); - auto capacity = tracker.capacity(); - if (size == capacity) { - nsecs_t nowNanos = systemTime(SYSTEM_TIME_MONOTONIC); - nsecs_t frameCompleteNanos = - tracker[0].get(FrameInfoIndex::FrameCompleted); - nsecs_t frameDiffNanos = nowNanos - frameCompleteNanos; - nsecs_t cleanupMillis = ns2ms(std::max(frameDiffNanos, 10_s)); - mRenderThread.cacheManager().performDeferredCleanup(cleanupMillis); + if (didDraw) { + int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime); + int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime; + int64_t actualDuration = frameDuration - + (std::min(syncDelayDuration, mLastDequeueBufferDuration)) - + dequeueBufferDuration - idleDuration; + mHintSessionWrapper.reportActualWorkDuration(actualDuration); } + + mLastDequeueBufferDuration = dequeueBufferDuration; + + mRenderThread.cacheManager().onFrameCompleted(); + return; } void CanvasContext::reportMetricsWithPresentTime() { @@ -777,6 +807,8 @@ void CanvasContext::onSurfaceStatsAvailable(void* context, int32_t surfaceContro // Called by choreographer to do an RT-driven animation void CanvasContext::doFrame() { if (!mRenderPipeline->isSurfaceReady()) return; + mIdleDuration = + systemTime(SYSTEM_TIME_MONOTONIC) - mRenderThread.timeLord().computeFrameTimeNanos(); prepareAndDraw(nullptr); } @@ -790,6 +822,7 @@ SkISize CanvasContext::getNextFrameSize() const { SkISize size; size.fWidth = ANativeWindow_getWidth(anw); size.fHeight = ANativeWindow_getHeight(anw); + mRenderThread.cacheManager().notifyNextFrameSize(size.fWidth, size.fHeight); return size; } @@ -868,18 +901,6 @@ void CanvasContext::destroyHardwareResources() { } } -void CanvasContext::trimMemory(RenderThread& thread, int level) { - ATRACE_CALL(); - if (!thread.getGrContext()) return; - ATRACE_CALL(); - if (level >= TRIM_MEMORY_COMPLETE) { - thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete); - thread.destroyRenderingContext(); - } else if (level >= TRIM_MEMORY_UI_HIDDEN) { - thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden); - } -} - DeferredLayerUpdater* CanvasContext::createTextureLayer() { return mRenderPipeline->createTextureLayer(); } @@ -996,6 +1017,14 @@ void CanvasContext::prepareSurfaceControlForWebview() { } } +void CanvasContext::sendLoadResetHint() { + mHintSessionWrapper.sendLoadResetHint(); +} + +void CanvasContext::setSyncDelayDuration(nsecs_t duration) { + mSyncDelayDuration = duration; +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 951ee216ce35..3f796d9b7b65 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -16,10 +16,26 @@ #pragma once +#include <SkBitmap.h> +#include <SkRect.h> +#include <SkSize.h> +#include <cutils/compiler.h> +#include <utils/Functor.h> +#include <utils/Mutex.h> + +#include <functional> +#include <future> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "ColorMode.h" #include "DamageAccumulator.h" #include "FrameInfo.h" #include "FrameInfoVisualizer.h" #include "FrameMetricsReporter.h" +#include "HintSessionWrapper.h" #include "IContextFactory.h" #include "IRenderPipeline.h" #include "JankTracker.h" @@ -30,21 +46,6 @@ #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" #include "utils/RingBuffer.h" -#include "ColorMode.h" - -#include <SkBitmap.h> -#include <SkRect.h> -#include <SkSize.h> -#include <cutils/compiler.h> -#include <utils/Functor.h> -#include <utils/Mutex.h> - -#include <functional> -#include <future> -#include <set> -#include <string> -#include <utility> -#include <vector> namespace android { namespace uirenderer { @@ -66,7 +67,8 @@ class Frame; class CanvasContext : public IFrameCallback { public: static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, - IContextFactory* contextFactory); + IContextFactory* contextFactory, pid_t uiThreadId, + pid_t renderThreadId); virtual ~CanvasContext(); /** @@ -123,11 +125,13 @@ public: // Won't take effect until next EGLSurface creation void setSwapBehavior(SwapBehavior swapBehavior); + void setHardwareBuffer(AHardwareBuffer* buffer); void setSurface(ANativeWindow* window, bool enableTimeout = true); void setSurfaceControl(ASurfaceControl* surfaceControl); bool pauseSurface(); void setStopped(bool stopped); - bool hasSurface() const { return mNativeSurface.get(); } + bool isStopped() { return mStopped || !hasOutputTarget(); } + bool hasOutputTarget() const { return mNativeSurface.get() || mHardwareBuffer; } void allocateBuffers(); void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); @@ -137,7 +141,7 @@ public: bool makeCurrent(); void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target); // Returns the DequeueBufferDuration. - nsecs_t draw(); + void draw(); void destroy(); // IFrameCallback, Choreographer-driven frame callback entry point @@ -148,7 +152,6 @@ public: void markLayerInUse(RenderNode* node); void destroyHardwareResources(); - static void trimMemory(RenderThread& thread, int level); DeferredLayerUpdater* createTextureLayer(); @@ -204,6 +207,10 @@ public: mASurfaceTransactionCallback = callback; } + void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { + mBufferParams = params; + } + bool mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control); void setPrepareSurfaceControlForWebviewCallback(const std::function<void()>& callback) { @@ -214,9 +221,14 @@ public: static CanvasContext* getActiveContext(); + void sendLoadResetHint(); + + void setSyncDelayDuration(nsecs_t duration); + private: CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, - IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline); + IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline, + pid_t uiThreadId, pid_t renderThreadId); friend class RegisterFrameCallbackTask; // TODO: Replace with something better for layer & other GL object @@ -251,6 +263,9 @@ private: int32_t mLastFrameHeight = 0; RenderThread& mRenderThread; + + AHardwareBuffer* mHardwareBuffer = nullptr; + HardwareBufferRenderParams mBufferParams; std::unique_ptr<ReliableSurface> mNativeSurface; // The SurfaceControl reference is passed from ViewRootImpl, can be set to // NULL to remove the reference @@ -331,7 +346,10 @@ private: std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback; std::function<void()> mPrepareSurfaceControlForWebviewCallback; - void cleanupResources(); + HintSessionWrapper mHintSessionWrapper; + nsecs_t mLastDequeueBufferDuration = 0; + nsecs_t mSyncDelayDuration = 0; + nsecs_t mIdleDuration = 0; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 03f02de98efe..b06c5dd9ad36 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -16,9 +16,9 @@ #include "DrawFrameTask.h" -#include <dlfcn.h> #include <gui/TraceUtils.h> #include <utils/Log.h> + #include <algorithm> #include "../DeferredLayerUpdater.h" @@ -26,64 +26,13 @@ #include "../Properties.h" #include "../RenderNode.h" #include "CanvasContext.h" +#include "HardwareBufferRenderParams.h" #include "RenderThread.h" -#include "thread/CommonPool.h" namespace android { namespace uirenderer { namespace renderthread { -namespace { - -typedef APerformanceHintManager* (*APH_getManager)(); -typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*, - size_t, int64_t); -typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t); -typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t); -typedef void (*APH_closeSession)(APerformanceHintSession* session); - -bool gAPerformanceHintBindingInitialized = false; -APH_getManager gAPH_getManagerFn = nullptr; -APH_createSession gAPH_createSessionFn = nullptr; -APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr; -APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr; -APH_closeSession gAPH_closeSessionFn = nullptr; - -void ensureAPerformanceHintBindingInitialized() { - if (gAPerformanceHintBindingInitialized) return; - - void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); - LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!"); - - gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager"); - LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr, - "Failed to find required symbol APerformanceHint_getManager!"); - - gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession"); - LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr, - "Failed to find required symbol APerformanceHint_createSession!"); - - gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym( - handle_, "APerformanceHint_updateTargetWorkDuration"); - LOG_ALWAYS_FATAL_IF( - gAPH_updateTargetWorkDurationFn == nullptr, - "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!"); - - gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym( - handle_, "APerformanceHint_reportActualWorkDuration"); - LOG_ALWAYS_FATAL_IF( - gAPH_reportActualWorkDurationFn == nullptr, - "Failed to find required symbol APerformanceHint_reportActualWorkDuration!"); - - gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession"); - LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr, - "Failed to find required symbol APerformanceHint_closeSession!"); - - gAPerformanceHintBindingInitialized = true; -} - -} // namespace - DrawFrameTask::DrawFrameTask() : mRenderThread(nullptr) , mContext(nullptr) @@ -92,13 +41,11 @@ DrawFrameTask::DrawFrameTask() DrawFrameTask::~DrawFrameTask() {} -void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode, - int32_t uiThreadId, int32_t renderThreadId) { +void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context, + RenderNode* targetNode) { mRenderThread = thread; mContext = context; mTargetNode = targetNode; - mUiThreadId = uiThreadId; - mRenderThreadId = renderThreadId; } void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) { @@ -142,8 +89,12 @@ void DrawFrameTask::postAndWait() { void DrawFrameTask::run() { const int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)]; ATRACE_FORMAT("DrawFrames %" PRId64, vsyncId); - nsecs_t syncDelayDuration = systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued; + mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued); + + auto hardwareBufferParams = mHardwareBufferParams; + mContext->setHardwareBufferRenderParams(hardwareBufferParams); + IRenderPipeline* pipeline = mContext->getRenderPipeline(); bool canUnblockUiThread; bool canDrawThisFrame; { @@ -166,9 +117,6 @@ void DrawFrameTask::run() { std::function<void()> frameCompleteCallback = std::move(mFrameCompleteCallback); mFrameCallback = nullptr; mFrameCompleteCallback = nullptr; - int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)]; - int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)]; - int64_t frameStartTime = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameStartTime)]; // From this point on anything in "this" is *UNSAFE TO ACCESS* if (canUnblockUiThread) { @@ -179,16 +127,15 @@ void DrawFrameTask::run() { if (CC_UNLIKELY(frameCallback)) { context->enqueueFrameWork([frameCallback, context, syncResult = mSyncResult, frameNr = context->getFrameNumber()]() { - auto frameCommitCallback = std::move(frameCallback(syncResult, frameNr)); + auto frameCommitCallback = frameCallback(syncResult, frameNr); if (frameCommitCallback) { context->addFrameCommitListener(std::move(frameCommitCallback)); } }); } - nsecs_t dequeueBufferDuration = 0; if (CC_LIKELY(canDrawThisFrame)) { - dequeueBufferDuration = context->draw(); + context->draw(); } else { // Do a flush in case syncFrameState performed any texture uploads. Since we skipped // the draw() call, those uploads (or deletes) will end up sitting in the queue. @@ -208,26 +155,10 @@ void DrawFrameTask::run() { unblockUiThread(); } - if (!mHintSessionWrapper) mHintSessionWrapper.emplace(mUiThreadId, mRenderThreadId); - constexpr int64_t kSanityCheckLowerBound = 100000; // 0.1ms - constexpr int64_t kSanityCheckUpperBound = 10000000000; // 10s - int64_t targetWorkDuration = frameDeadline - intendedVsync; - targetWorkDuration = targetWorkDuration * Properties::targetCpuTimePercentage / 100; - if (targetWorkDuration > kSanityCheckLowerBound && - targetWorkDuration < kSanityCheckUpperBound && - targetWorkDuration != mLastTargetWorkDuration) { - mLastTargetWorkDuration = targetWorkDuration; - mHintSessionWrapper->updateTargetWorkDuration(targetWorkDuration); - } - int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime; - int64_t actualDuration = frameDuration - - (std::min(syncDelayDuration, mLastDequeueBufferDuration)) - - dequeueBufferDuration; - if (actualDuration > kSanityCheckLowerBound && actualDuration < kSanityCheckUpperBound) { - mHintSessionWrapper->reportActualWorkDuration(actualDuration); + if (pipeline->hasHardwareBuffer()) { + auto fence = pipeline->flush(); + hardwareBufferParams.invokeRenderCallback(std::move(fence), 0); } - - mLastDequeueBufferDuration = dequeueBufferDuration; } bool DrawFrameTask::syncFrameState(TreeInfo& info) { @@ -253,8 +184,9 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { // This is after the prepareTree so that any pending operations // (RenderNode tree state, prefetched layers, etc...) will be flushed. - if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) { - if (!mContext->hasSurface()) { + bool hasTarget = mContext->hasOutputTarget(); + if (CC_UNLIKELY(!hasTarget || !canDraw)) { + if (!hasTarget) { mSyncResult |= SyncResult::LostSurfaceRewardIfFound; } else { // If we have a surface but can't draw we must be stopped @@ -280,44 +212,6 @@ void DrawFrameTask::unblockUiThread() { mSignal.signal(); } -DrawFrameTask::HintSessionWrapper::HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId) { - if (!Properties::useHintManager) return; - if (uiThreadId < 0 || renderThreadId < 0) return; - - ensureAPerformanceHintBindingInitialized(); - - APerformanceHintManager* manager = gAPH_getManagerFn(); - if (!manager) return; - - std::vector<int32_t> tids = CommonPool::getThreadIds(); - tids.push_back(uiThreadId); - tids.push_back(renderThreadId); - - // DrawFrameTask code will always set a target duration before reporting actual durations. - // So this is just a placeholder value that's never used. - int64_t dummyTargetDurationNanos = 16666667; - mHintSession = - gAPH_createSessionFn(manager, tids.data(), tids.size(), dummyTargetDurationNanos); -} - -DrawFrameTask::HintSessionWrapper::~HintSessionWrapper() { - if (mHintSession) { - gAPH_closeSessionFn(mHintSession); - } -} - -void DrawFrameTask::HintSessionWrapper::updateTargetWorkDuration(long targetDurationNanos) { - if (mHintSession) { - gAPH_updateTargetWorkDurationFn(mHintSession, targetDurationNanos); - } -} - -void DrawFrameTask::HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) { - if (mHintSession) { - gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos); - } -} - } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index d6fc292d5900..c5c5fe280743 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -16,7 +16,6 @@ #ifndef DRAWFRAMETASK_H #define DRAWFRAMETASK_H -#include <android/performance_hint.h> #include <utils/Condition.h> #include <utils/Mutex.h> #include <utils/StrongPointer.h> @@ -28,8 +27,16 @@ #include "../Rect.h" #include "../TreeInfo.h" #include "RenderTask.h" +#include "SkColorSpace.h" +#include "SwapBehavior.h" +#include "utils/TimeUtils.h" +#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration +#include <android/hardware_buffer.h> +#endif +#include "HardwareBufferRenderParams.h" namespace android { + namespace uirenderer { class DeferredLayerUpdater; @@ -61,8 +68,7 @@ public: DrawFrameTask(); virtual ~DrawFrameTask(); - void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode, - int32_t uiThreadId, int32_t renderThreadId); + void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode); void setContentDrawBounds(int left, int top, int right, int bottom) { mContentDrawBounds.set(left, top, right, bottom); } @@ -90,19 +96,11 @@ public: void forceDrawNextFrame() { mForceDrawFrame = true; } -private: - class HintSessionWrapper { - public: - HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId); - ~HintSessionWrapper(); - - void updateTargetWorkDuration(long targetDurationNanos); - void reportActualWorkDuration(long actualDurationNanos); - - private: - APerformanceHintSession* mHintSession = nullptr; - }; + void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { + mHardwareBufferParams = params; + } +private: void postAndWait(); bool syncFrameState(TreeInfo& info); void unblockUiThread(); @@ -113,8 +111,6 @@ private: RenderThread* mRenderThread; CanvasContext* mContext; RenderNode* mTargetNode = nullptr; - int32_t mUiThreadId = -1; - int32_t mRenderThreadId = -1; Rect mContentDrawBounds; /********************************************* @@ -127,14 +123,11 @@ private: int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE]; + HardwareBufferRenderParams mHardwareBufferParams; std::function<std::function<void(bool)>(int32_t, int64_t)> mFrameCallback; std::function<void(bool)> mFrameCommitCallback; std::function<void()> mFrameCompleteCallback; - nsecs_t mLastDequeueBufferDuration = 0; - nsecs_t mLastTargetWorkDuration = 0; - std::optional<HintSessionWrapper> mHintSessionWrapper; - bool mForceDrawFrame = false; }; diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index fc6b28d2e1ad..b8f8c9267ad8 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -18,6 +18,7 @@ #include <EGL/egl.h> #include <EGL/eglext.h> +#include <SkColorSpace.h> #include <SkImageInfo.h> #include <SkRect.h> #include <cutils/compiler.h> diff --git a/libs/hwui/renderthread/HardwareBufferRenderParams.h b/libs/hwui/renderthread/HardwareBufferRenderParams.h new file mode 100644 index 000000000000..91fe3f6cf273 --- /dev/null +++ b/libs/hwui/renderthread/HardwareBufferRenderParams.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013 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. + */ +#ifndef HARDWAREBUFFERRENDERER_H_ +#define HARDWAREBUFFERRENDERER_H_ + +#include <android-base/unique_fd.h> +#include <android/hardware_buffer.h> + +#include "SkColorSpace.h" +#include "SkMatrix.h" +#include "SkSurface.h" + +namespace android { +namespace uirenderer { +namespace renderthread { + +using namespace android::uirenderer::renderthread; + +using RenderCallback = std::function<void(android::base::unique_fd&&, int)>; + +class RenderProxy; + +class HardwareBufferRenderParams { +public: + HardwareBufferRenderParams() = default; + HardwareBufferRenderParams(const SkMatrix& transform, const sk_sp<SkColorSpace>& colorSpace, + RenderCallback&& callback) + : mTransform(transform) + , mColorSpace(colorSpace) + , mRenderCallback(std::move(callback)) {} + const SkMatrix& getTransform() const { return mTransform; } + sk_sp<SkColorSpace> getColorSpace() const { return mColorSpace; } + + void invokeRenderCallback(android::base::unique_fd&& fenceFd, int status) { + if (mRenderCallback) { + std::invoke(mRenderCallback, std::move(fenceFd), status); + } + } + +private: + SkMatrix mTransform = SkMatrix::I(); + sk_sp<SkColorSpace> mColorSpace = SkColorSpace::MakeSRGB(); + RenderCallback mRenderCallback = nullptr; +}; + +} // namespace renderthread +} // namespace uirenderer +} // namespace android +#endif // HARDWAREBUFFERRENDERER_H_ diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp new file mode 100644 index 000000000000..94c9d94a7c26 --- /dev/null +++ b/libs/hwui/renderthread/HintSessionWrapper.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "HintSessionWrapper.h" + +#include <dlfcn.h> +#include <private/performance_hint_private.h> +#include <utils/Log.h> + +#include <vector> + +#include "../Properties.h" +#include "thread/CommonPool.h" + +namespace android { +namespace uirenderer { +namespace renderthread { + +namespace { + +typedef APerformanceHintManager* (*APH_getManager)(); +typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*, + size_t, int64_t); +typedef void (*APH_closeSession)(APerformanceHintSession* session); +typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t); +typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t); +typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t); + +bool gAPerformanceHintBindingInitialized = false; +APH_getManager gAPH_getManagerFn = nullptr; +APH_createSession gAPH_createSessionFn = nullptr; +APH_closeSession gAPH_closeSessionFn = nullptr; +APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr; +APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr; +APH_sendHint gAPH_sendHintFn = nullptr; + +void ensureAPerformanceHintBindingInitialized() { + if (gAPerformanceHintBindingInitialized) return; + + void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); + LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!"); + + gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager"); + LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr, + "Failed to find required symbol APerformanceHint_getManager!"); + + gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession"); + LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr, + "Failed to find required symbol APerformanceHint_createSession!"); + + gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession"); + LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr, + "Failed to find required symbol APerformanceHint_closeSession!"); + + gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym( + handle_, "APerformanceHint_updateTargetWorkDuration"); + LOG_ALWAYS_FATAL_IF( + gAPH_updateTargetWorkDurationFn == nullptr, + "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!"); + + gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym( + handle_, "APerformanceHint_reportActualWorkDuration"); + LOG_ALWAYS_FATAL_IF( + gAPH_reportActualWorkDurationFn == nullptr, + "Failed to find required symbol APerformanceHint_reportActualWorkDuration!"); + + gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint"); + LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr, + "Failed to find required symbol APerformanceHint_sendHint!"); + + gAPerformanceHintBindingInitialized = true; +} + +} // namespace + +HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId) + : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {} + +HintSessionWrapper::~HintSessionWrapper() { + if (mHintSession) { + gAPH_closeSessionFn(mHintSession); + } +} + +bool HintSessionWrapper::useHintSession() { + if (!Properties::useHintManager || !Properties::isDrawingEnabled()) return false; + if (mHintSession) return true; + // If session does not exist, create it; + // this defers session creation until we try to actually use it. + if (!mSessionValid) return false; + return init(); +} + +bool HintSessionWrapper::init() { + if (mUiThreadId < 0 || mRenderThreadId < 0) return false; + + // Assume that if we return before the end, it broke + mSessionValid = false; + + ensureAPerformanceHintBindingInitialized(); + + APerformanceHintManager* manager = gAPH_getManagerFn(); + if (!manager) return false; + + std::vector<pid_t> tids = CommonPool::getThreadIds(); + tids.push_back(mUiThreadId); + tids.push_back(mRenderThreadId); + + // Use a placeholder target value to initialize, + // this will always be replaced elsewhere before it gets used + int64_t defaultTargetDurationNanos = 16666667; + mHintSession = + gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos); + + mSessionValid = !!mHintSession; + return mSessionValid; +} + +void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) { + if (!useHintSession()) return; + targetWorkDurationNanos = targetWorkDurationNanos * Properties::targetCpuTimePercentage / 100; + if (targetWorkDurationNanos != mLastTargetWorkDuration && + targetWorkDurationNanos > kSanityCheckLowerBound && + targetWorkDurationNanos < kSanityCheckUpperBound) { + mLastTargetWorkDuration = targetWorkDurationNanos; + gAPH_updateTargetWorkDurationFn(mHintSession, targetWorkDurationNanos); + } + mLastFrameNotification = systemTime(); +} + +void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) { + if (!useHintSession()) return; + if (actualDurationNanos > kSanityCheckLowerBound && + actualDurationNanos < kSanityCheckUpperBound) { + gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos); + } +} + +void HintSessionWrapper::sendLoadResetHint() { + if (!useHintSession()) return; + nsecs_t now = systemTime(); + if (now - mLastFrameNotification > kResetHintTimeout) { + gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET)); + } + mLastFrameNotification = now; +} + +} /* namespace renderthread */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h new file mode 100644 index 000000000000..fcbc10185255 --- /dev/null +++ b/libs/hwui/renderthread/HintSessionWrapper.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 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 <android/performance_hint.h> + +#include "utils/TimeUtils.h" + +namespace android { +namespace uirenderer { + +namespace renderthread { + +class HintSessionWrapper { +public: + HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId); + ~HintSessionWrapper(); + + void updateTargetWorkDuration(long targetDurationNanos); + void reportActualWorkDuration(long actualDurationNanos); + void sendLoadResetHint(); + +private: + bool useHintSession(); + bool init(); + APerformanceHintSession* mHintSession = nullptr; + + nsecs_t mLastFrameNotification = 0; + nsecs_t mLastTargetWorkDuration = 0; + + pid_t mUiThreadId; + pid_t mRenderThreadId; + + bool mSessionValid = true; + + static constexpr nsecs_t kResetHintTimeout = 100_ms; + static constexpr int64_t kSanityCheckLowerBound = 100_us; + static constexpr int64_t kSanityCheckUpperBound = 10_s; +}; + +} /* namespace renderthread */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index ef58bc553c23..715c17dfc895 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -16,16 +16,19 @@ #pragma once +#include <SkColorSpace.h> +#include <SkRect.h> +#include <android-base/unique_fd.h> +#include <utils/RefBase.h> + +#include "ColorMode.h" #include "DamageAccumulator.h" #include "FrameInfoVisualizer.h" +#include "HardwareBufferRenderParams.h" #include "LayerUpdateQueue.h" #include "Lighting.h" #include "SwapBehavior.h" #include "hwui/Bitmap.h" -#include "ColorMode.h" - -#include <SkRect.h> -#include <utils/RefBase.h> class GrDirectContext; @@ -63,10 +66,14 @@ public: const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, - FrameInfoVisualizer* profiler) = 0; + FrameInfoVisualizer* profiler, + const HardwareBufferRenderParams& bufferParams) = 0; virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; + [[nodiscard]] virtual android::base::unique_fd flush() = 0; + virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0; + virtual bool hasHardwareBuffer() = 0; virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index a44b498c81c1..ed01e322ffd9 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -29,6 +29,10 @@ #include "utils/Macros.h" #include "utils/TimeUtils.h" +#include <SkBitmap.h> +#include <SkImage.h> +#include <SkPicture.h> + #include <pthread.h> namespace android { @@ -38,11 +42,13 @@ namespace renderthread { RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) : mRenderThread(RenderThread::getInstance()), mContext(nullptr) { - mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* { - return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory); + pid_t uiThreadId = pthread_gettid_np(pthread_self()); + pid_t renderThreadId = getRenderThreadTid(); + mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* { + return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory, + uiThreadId, renderThreadId); }); - mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode, - pthread_gettid_np(pthread_self()), getRenderThreadTid()); + mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode); } RenderProxy::~RenderProxy() { @@ -51,7 +57,7 @@ RenderProxy::~RenderProxy() { void RenderProxy::destroyContext() { if (mContext) { - mDrawFrameTask.setContext(nullptr, nullptr, nullptr, -1, -1); + mDrawFrameTask.setContext(nullptr, nullptr, nullptr); // This is also a fence as we need to be certain that there are no // outstanding mDrawFrame tasks posted before it is destroyed mRenderThread.queue().runSync([this]() { delete mContext; }); @@ -79,6 +85,18 @@ void RenderProxy::setName(const char* name) { mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); }); } +void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) { + if (buffer) { + AHardwareBuffer_acquire(buffer); + } + mRenderThread.queue().post([this, hardwareBuffer = buffer]() mutable { + mContext->setHardwareBuffer(hardwareBuffer); + if (hardwareBuffer) { + AHardwareBuffer_release(hardwareBuffer); + } + }); +} + void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) { if (window) { ANativeWindow_acquire(window); } mRenderThread.queue().post([this, win = window, enableTimeout]() mutable { @@ -192,7 +210,8 @@ void RenderProxy::trimMemory(int level) { // Avoid creating a RenderThread to do a trimMemory. if (RenderThread::hasInstance()) { RenderThread& thread = RenderThread::getInstance(); - thread.queue().post([&thread, level]() { CanvasContext::trimMemory(thread, level); }); + const auto trimLevel = static_cast<TrimLevel>(level); + thread.queue().post([&thread, trimLevel]() { thread.trimMemory(trimLevel); }); } } @@ -201,7 +220,7 @@ void RenderProxy::purgeCaches() { RenderThread& thread = RenderThread::getInstance(); thread.queue().post([&thread]() { if (thread.getGrContext()) { - thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete); + thread.cacheManager().trimMemory(TrimLevel::COMPLETE); } }); } @@ -231,6 +250,10 @@ void RenderProxy::notifyFramePending() { mRenderThread.queue().post([this]() { mContext->notifyFramePending(); }); } +void RenderProxy::notifyCallbackPending() { + mRenderThread.queue().post([this]() { mContext->sendLoadResetHint(); }); +} + void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) { mRenderThread.queue().runSync([&]() { std::lock_guard lock(mRenderThread.getJankDataMutex()); @@ -313,6 +336,10 @@ void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) mDrawFrameTask.setContentDrawBounds(left, top, right, bottom); } +void RenderProxy::setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { + mDrawFrameTask.setHardwareBufferRenderParams(params); +} + void RenderProxy::setPictureCapturedCallback( const std::function<void(sk_sp<SkPicture>&&)>& callback) { mRenderThread.queue().post( @@ -360,12 +387,13 @@ void RenderProxy::setForceDark(bool enable) { mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); }); } -int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom, - SkBitmap* bitmap) { +void RenderProxy::copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request) { auto& thread = RenderThread::getInstance(); - return static_cast<int>(thread.queue().runSync([&]() -> auto { - return thread.readback().copySurfaceInto(window, Rect(left, top, right, bottom), bitmap); - })); + ANativeWindow_acquire(window); + thread.queue().post([&thread, window, request = std::move(request)] { + thread.readback().copySurfaceInto(window, request); + ANativeWindow_release(window); + }); } void RenderProxy::prepareToDraw(Bitmap& bitmap) { diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index ee9efd46e307..17cf6650f87d 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -17,19 +17,25 @@ #ifndef RENDERPROXY_H_ #define RENDERPROXY_H_ -#include <SkBitmap.h> +#include <SkRefCnt.h> +#include <android/hardware_buffer.h> #include <android/native_window.h> -#include <cutils/compiler.h> #include <android/surface_control.h> +#include <cutils/compiler.h> #include <utils/Functor.h> #include "../FrameMetricsObserver.h" #include "../IContextFactory.h" #include "ColorMode.h" +#include "CopyRequest.h" #include "DrawFrameTask.h" #include "SwapBehavior.h" #include "hwui/Bitmap.h" +class SkBitmap; +class SkPicture; +class SkImage; + namespace android { class GraphicBuffer; class Surface; @@ -71,7 +77,7 @@ public: void setSwapBehavior(SwapBehavior swapBehavior); bool loadSystemProperties(); void setName(const char* name); - + void setHardwareBuffer(AHardwareBuffer* buffer); void setSurface(ANativeWindow* window, bool enableTimeout = true); void setSurfaceControl(ASurfaceControl* surfaceControl); void allocateBuffers(); @@ -79,6 +85,7 @@ public: void setStopped(bool stopped); void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); void setLightGeometry(const Vector3& lightCenter, float lightRadius); + void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params); void setOpaque(bool opaque); void setColorMode(ColorMode mode); int64_t* frameInfo(); @@ -104,6 +111,7 @@ public: static int maxTextureSize(); void stopDrawing(); void notifyFramePending(); + void notifyCallbackPending(); void dumpProfileInfo(int fd, int dumpFlags); // Not exported, only used for testing @@ -133,8 +141,7 @@ public: void removeFrameMetricsObserver(FrameMetricsObserver* observer); void setForceDark(bool enable); - static int copySurfaceInto(ANativeWindow* window, int left, int top, int right, - int bottom, SkBitmap* bitmap); + static void copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request); static void prepareToDraw(Bitmap& bitmap); static int copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 01b956cb3dd5..7a7f1abdd268 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -251,7 +251,7 @@ void RenderThread::initThreadLocals() { mEglManager = new EglManager(); mRenderState = new RenderState(*this); mVkManager = VulkanManager::getInstance(); - mCacheManager = new CacheManager(); + mCacheManager = new CacheManager(*this); } void RenderThread::setupFrameInterval() { @@ -266,7 +266,7 @@ void RenderThread::requireGlContext() { } mEglManager->initialize(); - sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface()); + sk_sp<const GrGLInterface> glInterface = GrGLMakeNativeInterface(); LOG_ALWAYS_FATAL_IF(!glInterface.get()); GrContextOptions options; @@ -453,6 +453,8 @@ bool RenderThread::threadLoop() { // next vsync (oops), so none of the callbacks are run. requestVsync(); } + + mCacheManager->onThreadIdle(); } return false; @@ -502,6 +504,11 @@ void RenderThread::preload() { HardwareBitmapUploader::initialize(); } +void RenderThread::trimMemory(TrimLevel level) { + ATRACE_CALL(); + cacheManager().trimMemory(level); +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index c1f6790b25b2..0a89e5e944f8 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -17,11 +17,11 @@ #ifndef RENDERTHREAD_H_ #define RENDERTHREAD_H_ -#include <surface_control_private.h> #include <GrDirectContext.h> #include <SkBitmap.h> #include <cutils/compiler.h> #include <private/android/choreographer.h> +#include <surface_control_private.h> #include <thread/ThreadBase.h> #include <utils/Looper.h> #include <utils/Thread.h> @@ -31,6 +31,7 @@ #include <set> #include "CacheManager.h" +#include "MemoryPolicy.h" #include "ProfileDataContainer.h" #include "RenderTask.h" #include "TimeLord.h" @@ -172,6 +173,8 @@ public: return mASurfaceControlFunctions; } + void trimMemory(TrimLevel level); + /** * isCurrent provides a way to query, if the caller is running on * the render thread. diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index b8c2bdf112f8..c5196eeccea3 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -51,7 +51,8 @@ typedef void(VKAPI_PTR* PFN_vkFrameBoundaryANDROID)(VkDevice device, VkSemaphore #include "VulkanSurface.h" #include "private/hwui/DrawVkInfo.h" -class GrVkExtensions; +#include <SkColorSpace.h> +#include <SkRefCnt.h> namespace android { namespace uirenderer { diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h index beb71b727f51..26486669e712 100644 --- a/libs/hwui/renderthread/VulkanSurface.h +++ b/libs/hwui/renderthread/VulkanSurface.h @@ -20,6 +20,7 @@ #include <system/window.h> #include <vulkan/vulkan.h> +#include <SkColorSpace.h> #include <SkRefCnt.h> #include <SkSize.h> diff --git a/libs/hwui/tests/common/CallCountingCanvas.h b/libs/hwui/tests/common/CallCountingCanvas.h index d3c41191eef1..dc36a2e01815 100644 --- a/libs/hwui/tests/common/CallCountingCanvas.h +++ b/libs/hwui/tests/common/CallCountingCanvas.h @@ -19,6 +19,8 @@ #include <SkCanvasVirtualEnforcer.h> #include <SkNoDrawCanvas.h> +enum class SkBlendMode; + namespace android { namespace uirenderer { namespace test { diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp index 898c64bd4159..fd596d998dfd 100644 --- a/libs/hwui/tests/common/TestContext.cpp +++ b/libs/hwui/tests/common/TestContext.cpp @@ -28,10 +28,11 @@ const ui::StaticDisplayInfo& getDisplayInfo() { #if HWUI_NULL_GPU info.density = 2.f; #else - const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken(); - LOG_ALWAYS_FATAL_IF(!token, "%s: No internal display", __FUNCTION__); + const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds(); + LOG_ALWAYS_FATAL_IF(ids.empty(), "%s: No displays", __FUNCTION__); - const status_t status = SurfaceComposerClient::getStaticDisplayInfo(token, &info); + const status_t status = + SurfaceComposerClient::getStaticDisplayInfo(ids.front().value, &info); LOG_ALWAYS_FATAL_IF(status, "%s: Failed to get display info", __FUNCTION__); #endif return info; @@ -48,7 +49,10 @@ const ui::DisplayMode& getActiveDisplayMode() { config.xDpi = config.yDpi = 320.f; config.refreshRate = 60.f; #else - const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken(); + const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds(); + LOG_ALWAYS_FATAL_IF(ids.empty(), "%s: No displays", __FUNCTION__); + + const sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); LOG_ALWAYS_FATAL_IF(!token, "%s: No internal display", __FUNCTION__); const status_t status = SurfaceComposerClient::getActiveDisplayMode(token, &config); diff --git a/libs/hwui/tests/common/TestListViewSceneBase.cpp b/libs/hwui/tests/common/TestListViewSceneBase.cpp index 43df4a0b1576..e70d44c9c60a 100644 --- a/libs/hwui/tests/common/TestListViewSceneBase.cpp +++ b/libs/hwui/tests/common/TestListViewSceneBase.cpp @@ -19,6 +19,8 @@ #include "TestContext.h" #include "TestUtils.h" +#include <SkBlendMode.h> + #include <utils/Color.h> namespace android { diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 491af4336f97..a4890ede8faa 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -26,7 +26,13 @@ #include <renderthread/VulkanManager.h> #include <utils/Unicode.h> +#include "SkCanvas.h" #include "SkColorData.h" +#include "SkMatrix.h" +#include "SkPath.h" +#include "SkPixmap.h" +#include "SkRect.h" +#include "SkSurface.h" #include "SkUnPreMultiply.h" namespace android { diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index fcaa745e9fc6..9d5c13e5cd75 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -28,10 +28,20 @@ #include <renderstate/RenderState.h> #include <renderthread/RenderThread.h> +#include <SkBitmap.h> +#include <SkColor.h> +#include <SkImageInfo.h> +#include <SkRefCnt.h> + #include <gtest/gtest.h> #include <memory> #include <unordered_map> +class SkCanvas; +class SkMatrix; +class SkPath; +struct SkRect; + namespace android { namespace uirenderer { diff --git a/libs/hwui/tests/common/scenes/BitmapFillrate.cpp b/libs/hwui/tests/common/scenes/BitmapFillrate.cpp index 5af7d43d7f66..19e87f851827 100644 --- a/libs/hwui/tests/common/scenes/BitmapFillrate.cpp +++ b/libs/hwui/tests/common/scenes/BitmapFillrate.cpp @@ -19,6 +19,7 @@ #include "utils/Color.h" #include <SkBitmap.h> +#include <SkBlendMode.h> using namespace android; using namespace android::uirenderer; diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp index 03aeb55f129b..a07cdf720b50 100644 --- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp +++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp @@ -14,7 +14,17 @@ * limitations under the License. */ -#include <SkImagePriv.h> +#include <SkBitmap.h> +#include <SkBlendMode.h> +#include <SkCanvas.h> +#include <SkImage.h> +#include <SkImageInfo.h> +#include <SkPaint.h> +#include <SkRect.h> +#include <SkRefCnt.h> +#include <SkSamplingOptions.h> +#include <SkShader.h> +#include <SkTileMode.h> #include "hwui/Paint.h" #include "TestSceneBase.h" #include "tests/common/BitmapAllocationTestUtils.h" diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp index 2a016ac1b5bc..3a1ea8c29963 100644 --- a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class ClippingAnimation; static TestScene::Registrar _RectGrid(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp index 4271d2f04b88..484289a8ef1d 100644 --- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp +++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp @@ -20,6 +20,8 @@ #include <hwui/Paint.h> #include <minikin/Layout.h> +#include <SkBlendMode.h> + #include <cstdio> class GlyphStressAnimation; diff --git a/libs/hwui/tests/common/scenes/HwBitmap565.cpp b/libs/hwui/tests/common/scenes/HwBitmap565.cpp index cbdb756b8fa7..de0ef6d595f8 100644 --- a/libs/hwui/tests/common/scenes/HwBitmap565.cpp +++ b/libs/hwui/tests/common/scenes/HwBitmap565.cpp @@ -18,6 +18,12 @@ #include "tests/common/BitmapAllocationTestUtils.h" #include "utils/Color.h" +#include <SkBitmap.h> +#include <SkBlendMode.h> +#include <SkCanvas.h> +#include <SkPaint.h> +#include <SkRefCnt.h> + class HwBitmap565; static TestScene::Registrar _HwBitmap565(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp index 564354f04674..dfdd0d8727b9 100644 --- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp +++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp @@ -17,6 +17,8 @@ #include "TestSceneBase.h" #include "utils/Color.h" +#include <SkBlendMode.h> +#include <SkColorSpace.h> #include <SkGradientShader.h> #include <SkImagePriv.h> #include <ui/PixelFormat.h> diff --git a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp index cac2fb3d8d5c..2955fb25ec2c 100644 --- a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp +++ b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class HwLayerAnimation; static TestScene::Registrar _HwLayer(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp index 77a59dfe6ba5..8c9a6147f47d 100644 --- a/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp +++ b/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class HwLayerSizeAnimation; static TestScene::Registrar _HwLayerSize(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/JankyScene.cpp b/libs/hwui/tests/common/scenes/JankyScene.cpp index f5e6b317529a..250b986e7e73 100644 --- a/libs/hwui/tests/common/scenes/JankyScene.cpp +++ b/libs/hwui/tests/common/scenes/JankyScene.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + #include <unistd.h> class JankyScene; diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp index 5eaf1853233a..f669dbc9323e 100644 --- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp @@ -17,6 +17,7 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" #include "hwui/Paint.h" +#include <SkBlendMode.h> #include <SkGradientShader.h> class ListOfFadedTextAnimation; diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp index d031923a112b..4a5d9468cd88 100644 --- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp @@ -17,7 +17,16 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" #include "hwui/Paint.h" +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkColor.h> #include <SkFont.h> +#include <SkFontTypes.h> +#include <SkPaint.h> +#include <SkPoint.h> +#include <SkRect.h> +#include <SkRefCnt.h> +#include <SkScalar.h> #include <cstdio> class ListViewAnimation; @@ -48,7 +57,7 @@ class ListViewAnimation : public TestListViewSceneBase { 128 * 3; paint.setColor(bgDark ? Color::White : Color::Grey_700); - SkFont font; + SkFont font; font.setSize(size / 2); char charToShow = 'A' + (rand() % 26); const SkPoint pos = {SkIntToScalar(size / 2), diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp index edadf78db051..13a438199ae5 100644 --- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp +++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp @@ -19,19 +19,57 @@ #include "utils/Color.h" #include "hwui/Paint.h" +#include <SkBitmap.h> +#include <SkBlendMode.h> +#include <SkFont.h> + class MagnifierAnimation; +using Rect = android::uirenderer::Rect; + static TestScene::Registrar _Magnifier(TestScene::Info{ "magnifier", "A sample magnifier using Readback", TestScene::simpleCreateScene<MagnifierAnimation>}); +class BlockingCopyRequest : public CopyRequest { + sk_sp<Bitmap> mDestination; + std::mutex mLock; + std::condition_variable mCondVar; + CopyResult mResult; + +public: + BlockingCopyRequest(::Rect rect, sk_sp<Bitmap> bitmap) + : CopyRequest(rect), mDestination(bitmap) {} + + virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override { + SkBitmap bitmap; + mDestination->getSkBitmap(&bitmap); + return bitmap; + } + + virtual void onCopyFinished(CopyResult result) override { + std::unique_lock _lock{mLock}; + mResult = result; + mCondVar.notify_all(); + } + + CopyResult waitForResult() { + std::unique_lock _lock{mLock}; + mCondVar.wait(_lock); + return mResult; + } +}; + class MagnifierAnimation : public TestScene { public: sp<RenderNode> card; sp<RenderNode> zoomImageView; + sk_sp<Bitmap> magnifier; + std::shared_ptr<BlockingCopyRequest> copyRequest; void createContent(int width, int height, Canvas& canvas) override { magnifier = TestUtils::createBitmap(200, 100); + setupCopyRequest(); SkBitmap temp; magnifier->getSkBitmap(&temp); temp.eraseColor(Color::White); @@ -61,19 +99,20 @@ public: canvas.enableZ(false); } + void setupCopyRequest() { + constexpr int x = 90; + constexpr int y = 325; + copyRequest = std::make_shared<BlockingCopyRequest>( + ::Rect(x, y, x + magnifier->width(), y + magnifier->height()), magnifier); + } + void doFrame(int frameNr) override { int curFrame = frameNr % 150; card->mutateStagingProperties().setTranslationX(curFrame); card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); if (renderTarget) { - SkBitmap temp; - magnifier->getSkBitmap(&temp); - constexpr int x = 90; - constexpr int y = 325; - RenderProxy::copySurfaceInto(renderTarget.get(), x, y, x + magnifier->width(), - y + magnifier->height(), &temp); + RenderProxy::copySurfaceInto(renderTarget.get(), copyRequest); + copyRequest->waitForResult(); } } - - sk_sp<Bitmap> magnifier; }; diff --git a/libs/hwui/tests/common/scenes/OvalAnimation.cpp b/libs/hwui/tests/common/scenes/OvalAnimation.cpp index 402c1ece2146..1a2af8382ad7 100644 --- a/libs/hwui/tests/common/scenes/OvalAnimation.cpp +++ b/libs/hwui/tests/common/scenes/OvalAnimation.cpp @@ -17,6 +17,8 @@ #include "TestSceneBase.h" #include "utils/Color.h" +#include <SkBlendMode.h> + class OvalAnimation; static TestScene::Registrar _Oval(TestScene::Info{"oval", "Draws 1 oval.", diff --git a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp index fb1b000a995e..25cf4d61bf9d 100644 --- a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp +++ b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class PartialDamageAnimation; static TestScene::Registrar _PartialDamage(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp index 1e343c1dd283..969514c50d14 100644 --- a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp +++ b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp @@ -16,6 +16,8 @@ #include <vector> +#include <SkBlendMode.h> + #include "TestSceneBase.h" class PathClippingAnimation : public TestScene { diff --git a/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp b/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp index 716d3979bdcb..3caaf8236d8a 100644 --- a/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp +++ b/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp @@ -16,6 +16,12 @@ #include "TestSceneBase.h" +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkPaint.h> +#include <SkRect.h> +#include <SkRefCnt.h> + class ReadbackFromHardware; static TestScene::Registrar _SaveLayer(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/RecentsAnimation.cpp b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp index 1c2507867f6e..27948f8b4b43 100644 --- a/libs/hwui/tests/common/scenes/RecentsAnimation.cpp +++ b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp @@ -17,6 +17,11 @@ #include "TestSceneBase.h" #include "utils/Color.h" +#include <SkBitmap.h> +#include <SkBlendMode.h> +#include <SkColor.h> +#include <SkRefCnt.h> + class RecentsAnimation; static TestScene::Registrar _Recents(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp index f37bcbc3ee1b..99e785887b16 100644 --- a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp +++ b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class RectGridAnimation; static TestScene::Registrar _RectGrid(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp index e9f353d887f2..2c27969487d3 100644 --- a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp +++ b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + #include <vector> class RoundRectClippingAnimation : public TestScene { diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp index 252f539ffca9..ee30c131efbd 100644 --- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp @@ -16,6 +16,7 @@ #include <hwui/Paint.h> #include <minikin/Layout.h> +#include <SkBlendMode.h> #include <string> #include "TestSceneBase.h" diff --git a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp index 31a8ae1d38cd..d5060c758f93 100644 --- a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class SaveLayerAnimation; static TestScene::Registrar _SaveLayer(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp index c13e80e8c204..827ddab118d9 100644 --- a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp +++ b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class ShadowGrid2Animation; static TestScene::Registrar _ShadowGrid2(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp index 772b98e32220..a4fb10c5081e 100644 --- a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class ShadowGridAnimation; static TestScene::Registrar _ShadowGrid(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp index 0019da5fd80b..58c03727bc29 100644 --- a/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class ShadowShaderAnimation; static TestScene::Registrar _ShadowShader(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp index 70a1557dcf6a..c0c3dfd9a8c4 100644 --- a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp @@ -17,6 +17,8 @@ #include "TestSceneBase.h" #include "utils/Color.h" +#include <SkBlendMode.h> + #include <cstdio> class ShapeAnimation; diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp index a0bc5aa245d5..40f2ed081626 100644 --- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp +++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp @@ -16,7 +16,9 @@ #include "TestSceneBase.h" -#include <SkColorMatrixFilter.h> +#include <SkBlendMode.h> +#include <SkColorFilter.h> +#include <SkColorMatrix.h> #include <SkGradientShader.h> class SimpleColorMatrixAnimation; diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp index 57a260c8d234..a9e7a34b5b3f 100644 --- a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp +++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp @@ -16,6 +16,7 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> #include <SkGradientShader.h> class SimpleGradientAnimation; diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp index e677549b7894..bb95490c1d39 100644 --- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp @@ -14,7 +14,16 @@ * limitations under the License. */ +#include <SkBitmap.h> +#include <SkBlendMode.h> +#include <SkCanvas.h> +#include <SkColor.h> #include <SkFont.h> +#include <SkFontTypes.h> +#include <SkPaint.h> +#include <SkPoint.h> +#include <SkRefCnt.h> +#include <SkRRect.h> #include <cstdio> #include "TestSceneBase.h" #include "hwui/Paint.h" @@ -130,7 +139,7 @@ private: roundRectPaint.setColor(Color::White); if (addHolePunch) { // Punch a hole but then cover it up, we don't want to actually see it - canvas.punchHole(SkRRect::MakeRect(SkRect::MakeWH(itemWidth, itemHeight))); + canvas.punchHole(SkRRect::MakeRect(SkRect::MakeWH(itemWidth, itemHeight)), 1.f); } canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint); @@ -227,4 +236,4 @@ class StretchyUniformLayerListViewHolePunch : public StretchyListViewAnimation { StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; } bool haveHolePunch() override { return true; } bool forceLayer() override { return true; } -};
\ No newline at end of file +}; diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp index d30903679bce..78146b8cabf2 100644 --- a/libs/hwui/tests/common/scenes/TextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp @@ -17,6 +17,8 @@ #include "TestSceneBase.h" #include "hwui/Paint.h" +#include <SkBlendMode.h> + class TextAnimation; static TestScene::Registrar _Text(TestScene::Info{"text", "Draws a bunch of text.", diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp index c6219c485b85..aff8ca1e26c7 100644 --- a/libs/hwui/tests/common/scenes/TvApp.cpp +++ b/libs/hwui/tests/common/scenes/TvApp.cpp @@ -14,7 +14,12 @@ * limitations under the License. */ +#include "SkBitmap.h" #include "SkBlendMode.h" +#include "SkColorFilter.h" +#include "SkFont.h" +#include "SkImageInfo.h" +#include "SkRefCnt.h" #include "TestSceneBase.h" #include "tests/common/BitmapAllocationTestUtils.h" #include "hwui/Paint.h" diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp index 9cd10759a834..a55b72534924 100644 --- a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp +++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp @@ -22,6 +22,8 @@ #include "pipeline/skia/SkiaDisplayList.h" #include "tests/common/TestUtils.h" +#include <SkBlendMode.h> + using namespace android; using namespace android::uirenderer; using namespace android::uirenderer::skiapipeline; diff --git a/libs/hwui/tests/microbench/RenderNodeBench.cpp b/libs/hwui/tests/microbench/RenderNodeBench.cpp index 6aed251481bf..72946c4abdf0 100644 --- a/libs/hwui/tests/microbench/RenderNodeBench.cpp +++ b/libs/hwui/tests/microbench/RenderNodeBench.cpp @@ -19,6 +19,8 @@ #include "hwui/Canvas.h" #include "RenderNode.h" +#include <SkBlendMode.h> + using namespace android; using namespace android::uirenderer; diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp index 2ec78a429481..138b3efd10ed 100644 --- a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp +++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp @@ -29,7 +29,7 @@ AHardwareBuffer* allocHardwareBuffer() { .height = 16, .layers = 1, .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, - .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY, + .usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, }; constexpr int kSucceeded = 0; int status = AHardwareBuffer_allocate(&desc, &buffer); diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp index fc84abb1d605..2b90bda87ecd 100644 --- a/libs/hwui/tests/unit/CacheManagerTests.cpp +++ b/libs/hwui/tests/unit/CacheManagerTests.cpp @@ -21,6 +21,7 @@ #include "tests/common/TestUtils.h" #include <SkImagePriv.h> +#include "include/gpu/GpuTypes.h" // from Skia using namespace android; using namespace android::uirenderer; @@ -45,7 +46,8 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) { while (getCacheUsage(grContext) <= renderThread.cacheManager().getBackgroundCacheSize()) { SkImageInfo info = SkImageInfo::MakeA8(width, height); - sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, SkBudgeted::kYes, info); + sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, skgpu::Budgeted::kYes, + info); surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT); grContext->flushAndSubmit(); @@ -59,7 +61,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) { ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext)); // attempt to trim all memory while we still hold strong refs - renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete); + renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE); ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes()); // free the surfaces @@ -76,11 +78,11 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) { ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() < purgeableBytes); // UI hidden and make sure only some got purged (unique should remain) - renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden); + renderThread.cacheManager().trimMemory(TrimLevel::UI_HIDDEN); ASSERT_TRUE(0 < grContext->getResourceCachePurgeableBytes()); ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() > getCacheUsage(grContext)); // complete and make sure all get purged - renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete); + renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE); ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes()); } diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp index 1771c3590e10..9e376e32f8ea 100644 --- a/libs/hwui/tests/unit/CanvasContextTests.cpp +++ b/libs/hwui/tests/unit/CanvasContextTests.cpp @@ -36,9 +36,9 @@ RENDERTHREAD_TEST(CanvasContext, create) { auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( - CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory)); + CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0)); - ASSERT_FALSE(canvasContext->hasSurface()); + ASSERT_FALSE(canvasContext->hasOutputTarget()); canvasContext->destroy(); } diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp index 2cf3456694b0..1f6edf36af25 100644 --- a/libs/hwui/tests/unit/CanvasOpTests.cpp +++ b/libs/hwui/tests/unit/CanvasOpTests.cpp @@ -23,9 +23,18 @@ #include <tests/common/CallCountingCanvas.h> -#include "SkPictureRecorder.h" +#include "SkBlendMode.h" +#include "SkBitmap.h" +#include "SkCanvas.h" #include "SkColor.h" +#include "SkImageInfo.h" #include "SkLatticeIter.h" +#include "SkPaint.h" +#include "SkPath.h" +#include "SkPictureRecorder.h" +#include "SkRRect.h" +#include "SkRect.h" +#include "SkRegion.h" #include "pipeline/skia/AnimatedDrawables.h" #include <SkNoDrawCanvas.h> diff --git a/libs/hwui/tests/unit/EglManagerTests.cpp b/libs/hwui/tests/unit/EglManagerTests.cpp index 7f2e1589ae6c..ec9ab90fa46b 100644 --- a/libs/hwui/tests/unit/EglManagerTests.cpp +++ b/libs/hwui/tests/unit/EglManagerTests.cpp @@ -20,6 +20,8 @@ #include "renderthread/RenderEffectCapabilityQuery.h" #include "tests/common/TestContext.h" +#include <SkColorSpace.h> + using namespace android; using namespace android::uirenderer; using namespace android::uirenderer::renderthread; diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h index 2a74afc5bb7a..96a0c6114682 100644 --- a/libs/hwui/tests/unit/FatalTestCanvas.h +++ b/libs/hwui/tests/unit/FatalTestCanvas.h @@ -19,6 +19,8 @@ #include <SkCanvas.h> #include <gtest/gtest.h> +class SkRRect; + namespace { class TestCanvasBase : public SkCanvas { diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index ec949b80ea55..596bd37e4cf5 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -17,6 +17,7 @@ #include <VectorDrawable.h> #include <gtest/gtest.h> +#include <SkBlendMode.h> #include <SkClipStack.h> #include <SkSurface_Base.h> #include <string.h> @@ -334,7 +335,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) { "A"); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( - CanvasContext::create(renderThread, false, parent.get(), &contextFactory)); + CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0)); TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get()); DamageAccumulator damageAccumulator; info.damageAccumulator = &damageAccumulator; @@ -398,7 +399,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { "A"); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( - CanvasContext::create(renderThread, false, parent.get(), &contextFactory)); + CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0)); TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get()); DamageAccumulator damageAccumulator; info.damageAccumulator = &damageAccumulator; @@ -518,7 +519,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) { // prepareTree is required to find, which receivers have backward projected nodes ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( - CanvasContext::create(renderThread, false, parent.get(), &contextFactory)); + CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0)); TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get()); DamageAccumulator damageAccumulator; info.damageAccumulator = &damageAccumulator; @@ -618,7 +619,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) { // prepareTree is required to find, which receivers have backward projected nodes ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( - CanvasContext::create(renderThread, false, parent.get(), &contextFactory)); + CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0)); TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get()); DamageAccumulator damageAccumulator; info.damageAccumulator = &damageAccumulator; @@ -634,7 +635,7 @@ namespace { static int drawNode(RenderThread& renderThread, const sp<RenderNode>& renderNode) { ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( - CanvasContext::create(renderThread, false, renderNode.get(), &contextFactory)); + CanvasContext::create(renderThread, false, renderNode.get(), &contextFactory, 0, 0)); TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get()); DamageAccumulator damageAccumulator; info.damageAccumulator = &damageAccumulator; diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index 61bd646b0a76..80796f4a4111 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -274,7 +274,7 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_nullableDisplayList) { auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( - CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory)); + CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0)); TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get()); DamageAccumulator damageAccumulator; info.damageAccumulator = &damageAccumulator; @@ -310,7 +310,7 @@ RENDERTHREAD_TEST(DISABLED_RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { }); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( - CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory)); + CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0)); canvasContext->setSurface(nullptr); TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get()); DamageAccumulator damageAccumulator; diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 974d85a453db..576e9466d322 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -25,6 +25,8 @@ #include <cstdint> #include "FileBlobCache.h" #include "pipeline/skia/ShaderCache.h" +#include <SkData.h> +#include <SkRefCnt.h> using namespace android::uirenderer::skiapipeline; diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp index dc1b2e668dd0..c1ddbd36bcfd 100644 --- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp +++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp @@ -16,9 +16,14 @@ #include "tests/common/TestUtils.h" +#include <SkBitmap.h> +#include <SkBlendMode.h> +#include <SkColor.h> #include <SkColorMatrixFilter.h> #include <SkColorSpace.h> -#include <SkImagePriv.h> +#include <SkImageInfo.h> +#include <SkPaint.h> +#include <SkPath.h> #include <SkPathOps.h> #include <SkShader.h> #include <gtest/gtest.h> diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp index dae3c9435712..87c52161d68e 100644 --- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp +++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp @@ -17,9 +17,19 @@ #include "tests/common/TestUtils.h" #include <hwui/Paint.h> +#include <SkAlphaType.h> +#include <SkBitmap.h> +#include <SkBlendMode.h> +#include <SkCanvas.h> #include <SkCanvasStateUtils.h> +#include <SkColor.h> +#include <SkColorSpace.h> +#include <SkColorType.h> +#include <SkImageInfo.h> #include <SkPicture.h> #include <SkPictureRecorder.h> +#include <SkRefCnt.h> +#include <SkSurface.h> #include <gtest/gtest.h> using namespace android; diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp index 3d5aca4bf05a..f825d7c5d9cc 100644 --- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp +++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp @@ -142,7 +142,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) { auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( - CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory)); + CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0)); TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get()); DamageAccumulator damageAccumulator; info.damageAccumulator = &damageAccumulator; @@ -201,7 +201,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscr auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( - CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory)); + CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0)); // Set up a Surface so that we can position the VectorDrawable offscreen. test::TestContext testContext; diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 7419f8fd89f1..4d0595e03da6 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -17,6 +17,7 @@ #include <VectorDrawable.h> #include <gtest/gtest.h> +#include <SkBlendMode.h> #include <SkClipStack.h> #include <SkSurface_Base.h> #include <string.h> diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp index 15ecf5831f3a..ced667eb76e5 100644 --- a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp +++ b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp @@ -17,6 +17,7 @@ #include <VectorDrawable.h> #include <gtest/gtest.h> +#include <SkCanvas.h> #include <SkClipStack.h> #include <SkSurface_Base.h> #include <string.h> diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp index ab23448ab93f..499afa039d1f 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -21,8 +21,11 @@ #include <sys/stat.h> #include <utils/Log.h> +#include "SkData.h" #include "SkFontMgr.h" +#include "SkRefCnt.h" #include "SkStream.h" +#include "SkTypeface.h" #include "hwui/MinikinSkia.h" #include "hwui/Typeface.h" @@ -61,7 +64,7 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) { std::vector<minikin::FontVariation>()); std::vector<std::shared_ptr<minikin::Font>> fonts; fonts.push_back(minikin::Font::Builder(font).build()); - return std::make_shared<minikin::FontFamily>(std::move(fonts)); + return minikin::FontFamily::create(std::move(fonts)); } std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const char* fileName) { @@ -70,7 +73,8 @@ std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const ch TEST(TypefaceTest, resolveDefault_and_setDefaultTest) { std::unique_ptr<Typeface> regular(Typeface::createFromFamilies( - makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, + nullptr /* fallback */)); EXPECT_EQ(regular.get(), Typeface::resolveDefault(regular.get())); // Keep the original to restore it later. @@ -348,24 +352,24 @@ TEST(TypefaceTest, createAbsolute) { TEST(TypefaceTest, createFromFamilies_Single) { // In Java, new // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build(); - std::unique_ptr<Typeface> regular( - Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, false)); + std::unique_ptr<Typeface> regular(Typeface::createFromFamilies( + makeSingleFamlyVector(kRobotoVariable), 400, false, nullptr /* fallback */)); EXPECT_EQ(400, regular->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant()); EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle); // In Java, new // Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(false).build(); - std::unique_ptr<Typeface> bold( - Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, false)); + std::unique_ptr<Typeface> bold(Typeface::createFromFamilies( + makeSingleFamlyVector(kRobotoVariable), 700, false, nullptr /* fallback */)); EXPECT_EQ(700, bold->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant()); EXPECT_EQ(Typeface::kBold, bold->fAPIStyle); // In Java, new // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(true).build(); - std::unique_ptr<Typeface> italic( - Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, true)); + std::unique_ptr<Typeface> italic(Typeface::createFromFamilies( + makeSingleFamlyVector(kRobotoVariable), 400, true, nullptr /* fallback */)); EXPECT_EQ(400, italic->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant()); EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle); @@ -373,8 +377,8 @@ TEST(TypefaceTest, createFromFamilies_Single) { // In Java, // new // Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(true).build(); - std::unique_ptr<Typeface> boldItalic( - Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, true)); + std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies( + makeSingleFamlyVector(kRobotoVariable), 700, true, nullptr /* fallback */)); EXPECT_EQ(700, boldItalic->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant()); EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle); @@ -382,8 +386,8 @@ TEST(TypefaceTest, createFromFamilies_Single) { // In Java, // new // Typeface.Builder("Roboto-Regular.ttf").setWeight(1100).setItalic(false).build(); - std::unique_ptr<Typeface> over1000( - Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 1100, false)); + std::unique_ptr<Typeface> over1000(Typeface::createFromFamilies( + makeSingleFamlyVector(kRobotoVariable), 1100, false, nullptr /* fallback */)); EXPECT_EQ(1000, over1000->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant()); EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle); @@ -391,30 +395,33 @@ TEST(TypefaceTest, createFromFamilies_Single) { TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) { // In Java, new Typeface.Builder("Family-Regular.ttf").build(); - std::unique_ptr<Typeface> regular(Typeface::createFromFamilies( - makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> regular( + Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); EXPECT_EQ(400, regular->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant()); EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle); // In Java, new Typeface.Builder("Family-Bold.ttf").build(); - std::unique_ptr<Typeface> bold(Typeface::createFromFamilies( - makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> bold( + Typeface::createFromFamilies(makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); EXPECT_EQ(700, bold->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant()); EXPECT_EQ(Typeface::kBold, bold->fAPIStyle); // In Java, new Typeface.Builder("Family-Italic.ttf").build(); - std::unique_ptr<Typeface> italic(Typeface::createFromFamilies( - makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> italic( + Typeface::createFromFamilies(makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); EXPECT_EQ(400, italic->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant()); EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle); // In Java, new Typeface.Builder("Family-BoldItalic.ttf").build(); - std::unique_ptr<Typeface> boldItalic( - Typeface::createFromFamilies(makeSingleFamlyVector(kBoldItalicFont), - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies( + makeSingleFamlyVector(kBoldItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, + nullptr /* fallback */)); EXPECT_EQ(700, boldItalic->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant()); EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle); @@ -424,8 +431,9 @@ TEST(TypefaceTest, createFromFamilies_Family) { std::vector<std::shared_ptr<minikin::FontFamily>> families = { buildFamily(kRegularFont), buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)}; - std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies( - std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> typeface( + Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); EXPECT_EQ(400, typeface->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant()); } @@ -433,10 +441,24 @@ TEST(TypefaceTest, createFromFamilies_Family) { TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) { std::vector<std::shared_ptr<minikin::FontFamily>> families = { buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)}; - std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies( - std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + std::unique_ptr<Typeface> typeface( + Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); EXPECT_EQ(700, typeface->fStyle.weight()); EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant()); } +TEST(TypefaceTest, createFromFamilies_Family_withFallback) { + std::vector<std::shared_ptr<minikin::FontFamily>> fallbackFamilies = { + buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)}; + std::unique_ptr<Typeface> fallback( + Typeface::createFromFamilies(std::move(fallbackFamilies), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, nullptr /* fallback */)); + std::unique_ptr<Typeface> regular( + Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, + RESOLVE_BY_FONT_TABLE, fallback.get())); + EXPECT_EQ(400, regular->fStyle.weight()); + EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant()); +} + } // namespace diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp index 6d4c57413f00..c1c21bd7dfbf 100644 --- a/libs/hwui/tests/unit/VectorDrawableTests.cpp +++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp @@ -21,6 +21,12 @@ #include "utils/MathUtils.h" #include "utils/VectorDrawableUtils.h" +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkPath.h> +#include <SkRefCnt.h> +#include <SkShader.h> + #include <functional> namespace android { diff --git a/libs/hwui/utils/AutoMalloc.h b/libs/hwui/utils/AutoMalloc.h new file mode 100644 index 000000000000..05f5e9f24133 --- /dev/null +++ b/libs/hwui/utils/AutoMalloc.h @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <cstdlib> +#include <memory> +#include <type_traits> + +namespace android { +namespace uirenderer { + +/** Manages an array of T elements, freeing the array in the destructor. + * Does NOT call any constructors/destructors on T (T must be POD). + */ +template <typename T, + typename = std::enable_if_t<std::is_trivially_default_constructible<T>::value && + std::is_trivially_destructible<T>::value>> +class AutoTMalloc { +public: + /** Takes ownership of the ptr. The ptr must be a value which can be passed to std::free. */ + explicit AutoTMalloc(T* ptr = nullptr) : fPtr(ptr) {} + + /** Allocates space for 'count' Ts. */ + explicit AutoTMalloc(size_t count) : fPtr(mallocIfCountThrowOnFail(count)) {} + + AutoTMalloc(AutoTMalloc&&) = default; + AutoTMalloc& operator=(AutoTMalloc&&) = default; + + /** Resize the memory area pointed to by the current ptr preserving contents. */ + void realloc(size_t count) { fPtr.reset(reallocIfCountThrowOnFail(count)); } + + /** Resize the memory area pointed to by the current ptr without preserving contents. */ + T* reset(size_t count = 0) { + fPtr.reset(mallocIfCountThrowOnFail(count)); + return this->get(); + } + + T* get() const { return fPtr.get(); } + + operator T*() { return fPtr.get(); } + + operator const T*() const { return fPtr.get(); } + + T& operator[](int index) { return fPtr.get()[index]; } + + const T& operator[](int index) const { return fPtr.get()[index]; } + + /** + * Transfer ownership of the ptr to the caller, setting the internal + * pointer to NULL. Note that this differs from get(), which also returns + * the pointer, but it does not transfer ownership. + */ + T* release() { return fPtr.release(); } + +private: + struct FreeDeleter { + void operator()(uint8_t* p) { std::free(p); } + }; + std::unique_ptr<T, FreeDeleter> fPtr; + + T* mallocIfCountThrowOnFail(size_t count) { + T* newPtr = nullptr; + if (count) { + newPtr = (T*)std::malloc(count * sizeof(T)); + LOG_ALWAYS_FATAL_IF(!newPtr, "failed to malloc %zu bytes", count * sizeof(T)); + } + return newPtr; + } + T* reallocIfCountThrowOnFail(size_t count) { + T* newPtr = nullptr; + if (count) { + newPtr = (T*)std::realloc(fPtr.release(), count * sizeof(T)); + LOG_ALWAYS_FATAL_IF(!newPtr, "failed to realloc %zu bytes", count * sizeof(T)); + } + return newPtr; + } +}; + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h index 94bcb1110e05..f44f9d0fe2d4 100644 --- a/libs/hwui/utils/PaintUtils.h +++ b/libs/hwui/utils/PaintUtils.h @@ -19,6 +19,7 @@ #include <GLES2/gl2.h> #include <utils/Blur.h> +#include <SkBlendMode.h> #include <SkColorFilter.h> #include <SkPaint.h> #include <SkShader.h> diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index 45da008c3e8e..a83516791f33 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -22,14 +22,9 @@ #include "MouseCursorController.h" +#include <input/Input.h> #include <log/log.h> -#include <SkBitmap.h> -#include <SkBlendMode.h> -#include <SkCanvas.h> -#include <SkColor.h> -#include <SkPaint.h> - namespace { // Time to spend fading out the pointer completely. const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms @@ -204,8 +199,7 @@ static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, i width = viewport.deviceWidth; height = viewport.deviceHeight; - if (viewport.orientation == DISPLAY_ORIENTATION_90 || - viewport.orientation == DISPLAY_ORIENTATION_270) { + if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) { std::swap(width, height); } } @@ -249,38 +243,42 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport, // Undo the previous rotation. switch (oldViewport.orientation) { - case DISPLAY_ORIENTATION_90: + case ui::ROTATION_90: temp = x; x = oldViewport.deviceHeight - y; y = temp; break; - case DISPLAY_ORIENTATION_180: + case ui::ROTATION_180: x = oldViewport.deviceWidth - x; y = oldViewport.deviceHeight - y; break; - case DISPLAY_ORIENTATION_270: + case ui::ROTATION_270: temp = x; x = y; y = oldViewport.deviceWidth - temp; break; + case ui::ROTATION_0: + break; } // Perform the new rotation. switch (viewport.orientation) { - case DISPLAY_ORIENTATION_90: + case ui::ROTATION_90: temp = x; x = y; y = viewport.deviceHeight - temp; break; - case DISPLAY_ORIENTATION_180: + case ui::ROTATION_180: x = viewport.deviceWidth - x; y = viewport.deviceHeight - y; break; - case DISPLAY_ORIENTATION_270: + case ui::ROTATION_270: temp = x; x = viewport.deviceWidth - y; y = temp; break; + case ui::ROTATION_0: + break; } // Apply offsets to convert from the pixel center to the pixel top-left corner position @@ -292,7 +290,7 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport, updatePointerLocked(); } -void MouseCursorController::updatePointerIcon(int32_t iconId) { +void MouseCursorController::updatePointerIcon(PointerIconStyle iconId) { std::scoped_lock lock(mLock); if (mLocked.requestedPointerType != iconId) { @@ -305,7 +303,7 @@ void MouseCursorController::updatePointerIcon(int32_t iconId) { void MouseCursorController::setCustomPointerIcon(const SpriteIcon& icon) { std::scoped_lock lock(mLock); - const int32_t iconId = mContext.getPolicy()->getCustomPointerIconId(); + const PointerIconStyle iconId = mContext.getPolicy()->getCustomPointerIconId(); mLocked.additionalMouseResources[iconId] = icon; mLocked.requestedPointerType = iconId; mLocked.updatePointerIcon = true; @@ -340,7 +338,7 @@ bool MouseCursorController::doFadingAnimationLocked(nsecs_t timestamp) REQUIRES( } bool MouseCursorController::doBitmapAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) { - std::map<int32_t, PointerAnimation>::const_iterator iter = + std::map<PointerIconStyle, PointerAnimation>::const_iterator iter = mLocked.animationResources.find(mLocked.requestedPointerType); if (iter == mLocked.animationResources.end()) { return false; @@ -386,10 +384,10 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) { if (mLocked.requestedPointerType == mContext.getPolicy()->getDefaultPointerIconId()) { mLocked.pointerSprite->setIcon(mLocked.pointerIcon); } else { - std::map<int32_t, SpriteIcon>::const_iterator iter = + std::map<PointerIconStyle, SpriteIcon>::const_iterator iter = mLocked.additionalMouseResources.find(mLocked.requestedPointerType); if (iter != mLocked.additionalMouseResources.end()) { - std::map<int32_t, PointerAnimation>::const_iterator anim_iter = + std::map<PointerIconStyle, PointerAnimation>::const_iterator anim_iter = mLocked.animationResources.find(mLocked.requestedPointerType); if (anim_iter != mLocked.animationResources.end()) { mLocked.animationFrameIndex = 0; diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index c0ab58bd2e7e..208d33d7c717 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -54,7 +54,7 @@ public: void unfade(PointerControllerInterface::Transition transition); void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources); - void updatePointerIcon(int32_t iconId); + void updatePointerIcon(PointerIconStyle iconId); void setCustomPointerIcon(const SpriteIcon& icon); void reloadPointerResources(bool getAdditionalMouseResources); @@ -88,10 +88,10 @@ private: bool resourcesLoaded; - std::map<int32_t, SpriteIcon> additionalMouseResources; - std::map<int32_t, PointerAnimation> animationResources; + std::map<PointerIconStyle, SpriteIcon> additionalMouseResources; + std::map<PointerIconStyle, PointerAnimation> animationResources; - int32_t requestedPointerType; + PointerIconStyle requestedPointerType; int32_t buttonState; diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 10ea6512c724..099efd3a1a2f 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -22,10 +22,18 @@ #include <SkBlendMode.h> #include <SkCanvas.h> #include <SkColor.h> +#include <android-base/stringprintf.h> #include <android-base/thread_annotations.h> +#include <ftl/enum.h> + +#include <mutex> #include "PointerControllerContext.h" +#define INDENT " " +#define INDENT2 " " +#define INDENT3 " " + namespace android { namespace { @@ -223,7 +231,7 @@ void PointerController::clearSpots() { } void PointerController::clearSpotsLocked() { - for (auto& [displayID, spotController] : mLocked.spotControllers) { + for (auto& [displayId, spotController] : mLocked.spotControllers) { spotController.clearSpots(); } } @@ -235,7 +243,7 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout void PointerController::reloadPointerResources() { std::scoped_lock lock(getLock()); - for (auto& [displayID, spotController] : mLocked.spotControllers) { + for (auto& [displayId, spotController] : mLocked.spotControllers) { spotController.reloadSpotResources(); } @@ -264,7 +272,7 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) { } } -void PointerController::updatePointerIcon(int32_t iconId) { +void PointerController::updatePointerIcon(PointerIconStyle iconId) { std::scoped_lock lock(getLock()); mCursorController.updatePointerIcon(iconId); } @@ -286,13 +294,13 @@ void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& std::scoped_lock lock(getLock()); for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) { - int32_t displayID = it->first; - if (!displayIdSet.count(displayID)) { + int32_t displayId = it->first; + if (!displayIdSet.count(displayId)) { /* * Ensures that an in-progress animation won't dereference * a null pointer to TouchSpotController. */ - mContext.removeAnimationCallback(displayID); + mContext.removeAnimationCallback(displayId); it = mLocked.spotControllers.erase(it); } else { ++it; @@ -313,4 +321,20 @@ const ui::Transform& PointerController::getTransformForDisplayLocked(int display return it != di.end() ? it->transform : kIdentityTransform; } +void PointerController::dump(std::string& dump) { + dump += INDENT "PointerController:\n"; + std::scoped_lock lock(getLock()); + dump += StringPrintf(INDENT2 "Presentation: %s\n", + ftl::enum_string(mLocked.presentation).c_str()); + dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId); + dump += StringPrintf(INDENT2 "Viewports:\n"); + for (const auto& info : mLocked.mDisplayInfos) { + info.dump(dump, INDENT3); + } + dump += INDENT2 "Spot Controllers:\n"; + for (const auto& [_, spotController] : mLocked.spotControllers) { + spotController.dump(dump, INDENT3); + } +} + } // namespace android diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index eab030f71e1a..48d5a5756a69 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -27,6 +27,7 @@ #include <map> #include <memory> +#include <string> #include <vector> #include "MouseCursorController.h" @@ -65,7 +66,7 @@ public: BitSet32 spotIdBits, int32_t displayId); virtual void clearSpots(); - void updatePointerIcon(int32_t iconId); + void updatePointerIcon(PointerIconStyle iconId); void setCustomPointerIcon(const SpriteIcon& icon); void setInactivityTimeout(InactivityTimeout inactivityTimeout); void doInactivityTimeout(); @@ -75,6 +76,8 @@ public: void onDisplayInfosChangedLocked(const std::vector<gui::DisplayInfo>& displayInfos) REQUIRES(getLock()); + void dump(std::string& dump); + protected: using WindowListenerConsumer = std::function<void(const sp<android::gui::WindowInfosListener>&)>; diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h index c2bc1e020279..1797428b343f 100644 --- a/libs/input/PointerControllerContext.h +++ b/libs/input/PointerControllerContext.h @@ -75,10 +75,11 @@ public: virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0; virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0; virtual void loadAdditionalMouseResources( - std::map<int32_t, SpriteIcon>* outResources, - std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0; - virtual int32_t getDefaultPointerIconId() = 0; - virtual int32_t getCustomPointerIconId() = 0; + std::map<PointerIconStyle, SpriteIcon>* outResources, + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, + int32_t displayId) = 0; + virtual PointerIconStyle getDefaultPointerIconId() = 0; + virtual PointerIconStyle getCustomPointerIconId() = 0; virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0; }; diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index 2b809eab4ae4..130b204954b4 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -131,8 +131,9 @@ void SpriteController::doUpdateSprites() { update.state.surfaceHeight = update.state.icon.height(); update.state.surfaceDrawn = false; update.state.surfaceVisible = false; - update.state.surfaceControl = obtainSurface( - update.state.surfaceWidth, update.state.surfaceHeight); + update.state.surfaceControl = + obtainSurface(update.state.surfaceWidth, update.state.surfaceHeight, + update.state.displayId); if (update.state.surfaceControl != NULL) { update.surfaceChanged = surfaceChanged = true; } @@ -168,8 +169,8 @@ void SpriteController::doUpdateSprites() { } } - // If surface is a new one, we have to set right layer stack. - if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) { + // If surface has changed to a new display, we have to reparent it. + if (update.state.dirty & DIRTY_DISPLAY_ID) { t.reparent(update.state.surfaceControl, mParentSurfaceProvider(update.state.displayId)); needApplyTransaction = true; } @@ -242,15 +243,14 @@ void SpriteController::doUpdateSprites() { && (becomingVisible || (update.state.dirty & (DIRTY_HOTSPOT | DIRTY_ICON_STYLE)))) { Parcel p; - p.writeInt32(update.state.icon.style); + p.writeInt32(static_cast<int32_t>(update.state.icon.style)); p.writeFloat(update.state.icon.hotSpotX); p.writeFloat(update.state.icon.hotSpotY); // Pass cursor metadata in the sprite surface so that when Android is running as a // client OS (e.g. ARC++) the host OS can get the requested cursor metadata and // update mouse cursor in the host OS. - t.setMetadata( - update.state.surfaceControl, METADATA_MOUSE_CURSOR, p); + t.setMetadata(update.state.surfaceControl, gui::METADATA_MOUSE_CURSOR, p); } int32_t surfaceLayer = mOverlayLayer + update.state.layer; @@ -331,21 +331,28 @@ void SpriteController::ensureSurfaceComposerClient() { } } -sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height) { +sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, + int32_t displayId) { ensureSurfaceComposerClient(); - sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface( - String8("Sprite"), width, height, PIXEL_FORMAT_RGBA_8888, - ISurfaceComposerClient::eHidden | - ISurfaceComposerClient::eCursorWindow); - if (surfaceControl == NULL || !surfaceControl->isValid()) { + const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId); + if (parent == nullptr) { + ALOGE("Failed to get the parent surface for pointers on display %d", displayId); + } + + const sp<SurfaceControl> surfaceControl = + mSurfaceComposerClient->createSurface(String8("Sprite"), width, height, + PIXEL_FORMAT_RGBA_8888, + ISurfaceComposerClient::eHidden | + ISurfaceComposerClient::eCursorWindow, + parent ? parent->getHandle() : nullptr); + if (surfaceControl == nullptr || !surfaceControl->isValid()) { ALOGE("Error creating sprite surface."); - return NULL; + return nullptr; } return surfaceControl; } - // --- SpriteController::SpriteImpl --- SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) : diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 2e9cb9685c46..1f113c045360 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -265,7 +265,7 @@ private: void doDisposeSurfaces(); void ensureSurfaceComposerClient(); - sp<SurfaceControl> obtainSurface(int32_t width, int32_t height); + sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId); }; } // namespace android diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h index a257d7e89ebc..5f085bbd2374 100644 --- a/libs/input/SpriteIcon.h +++ b/libs/input/SpriteIcon.h @@ -19,6 +19,7 @@ #include <android/graphics/bitmap.h> #include <gui/Surface.h> +#include <input/Input.h> namespace android { @@ -26,12 +27,13 @@ namespace android { * Icon that a sprite displays, including its hotspot. */ struct SpriteIcon { - inline SpriteIcon() : style(0), hotSpotX(0), hotSpotY(0) {} - inline SpriteIcon(const graphics::Bitmap& bitmap, int32_t style, float hotSpotX, float hotSpotY) + inline SpriteIcon() : style(PointerIconStyle::TYPE_NULL), hotSpotX(0), hotSpotY(0) {} + inline SpriteIcon(const graphics::Bitmap& bitmap, PointerIconStyle style, float hotSpotX, + float hotSpotY) : bitmap(bitmap), style(style), hotSpotX(hotSpotX), hotSpotY(hotSpotY) {} graphics::Bitmap bitmap; - int32_t style; + PointerIconStyle style; float hotSpotX; float hotSpotY; @@ -41,7 +43,7 @@ struct SpriteIcon { inline void reset() { bitmap.reset(); - style = 0; + style = PointerIconStyle::TYPE_NULL; hotSpotX = 0; hotSpotY = 0; } diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp index f7c685ff8ba6..d9fe5996bcff 100644 --- a/libs/input/TouchSpotController.cpp +++ b/libs/input/TouchSpotController.cpp @@ -21,13 +21,14 @@ #include "TouchSpotController.h" +#include <android-base/stringprintf.h> +#include <input/PrintTools.h> #include <log/log.h> -#include <SkBitmap.h> -#include <SkBlendMode.h> -#include <SkCanvas.h> -#include <SkColor.h> -#include <SkPaint.h> +#include <mutex> + +#define INDENT " " +#define INDENT2 " " namespace { // Time to spend fading out the spot completely. @@ -59,6 +60,12 @@ void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, fl } } +void TouchSpotController::Spot::dump(std::string& out, const char* prefix) const { + out += prefix; + base::StringAppendF(&out, "Spot{id=%" PRIx32 ", alpha=%f, scale=%f, pos=[%f, %f]}\n", id, alpha, + scale, x, y); +} + // --- TouchSpotController --- TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context) @@ -261,4 +268,22 @@ void TouchSpotController::startAnimationLocked() REQUIRES(mLock) { mContext.addAnimationCallback(mDisplayId, func); } +void TouchSpotController::dump(std::string& out, const char* prefix) const { + using base::StringAppendF; + out += prefix; + out += "SpotController:\n"; + out += prefix; + StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId); + std::scoped_lock lock(mLock); + out += prefix; + StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating)); + out += prefix; + out += INDENT "Spots:\n"; + std::string spotPrefix = prefix; + spotPrefix += INDENT2; + for (const auto& spot : mLocked.displaySpots) { + spot->dump(out, spotPrefix.c_str()); + } +} + } // namespace android diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h index 703de3603f48..5bbc75d9570b 100644 --- a/libs/input/TouchSpotController.h +++ b/libs/input/TouchSpotController.h @@ -38,6 +38,8 @@ public: void reloadSpotResources(); bool doAnimations(nsecs_t timestamp); + void dump(std::string& out, const char* prefix = "") const; + private: struct Spot { static const uint32_t INVALID_ID = 0xffffffff; @@ -58,6 +60,7 @@ private: mLastIcon(nullptr) {} void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId); + void dump(std::string& out, const char* prefix = "") const; private: const SpriteIcon* mLastIcon; diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index f9752ed155df..a6a4115476df 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -14,17 +14,18 @@ * limitations under the License. */ -#include "mocks/MockSprite.h" -#include "mocks/MockSpriteController.h" - +#include <gmock/gmock.h> +#include <gtest/gtest.h> #include <input/PointerController.h> #include <input/SpriteController.h> #include <atomic> -#include <gmock/gmock.h> -#include <gtest/gtest.h> #include <thread> +#include "input/Input.h" +#include "mocks/MockSprite.h" +#include "mocks/MockSpriteController.h" + namespace android { enum TestCursorType { @@ -39,7 +40,6 @@ enum TestCursorType { using ::testing::AllOf; using ::testing::Field; -using ::testing::Mock; using ::testing::NiceMock; using ::testing::Return; using ::testing::Test; @@ -52,10 +52,12 @@ class MockPointerControllerPolicyInterface : public PointerControllerPolicyInter public: virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override; virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override; - virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources, - std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override; - virtual int32_t getDefaultPointerIconId() override; - virtual int32_t getCustomPointerIconId() override; + virtual void loadAdditionalMouseResources( + std::map<PointerIconStyle, SpriteIcon>* outResources, + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, + int32_t displayId) override; + virtual PointerIconStyle getDefaultPointerIconId() override; + virtual PointerIconStyle getCustomPointerIconId() override; virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override; bool allResourcesAreLoaded(); @@ -85,34 +87,33 @@ void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources } void MockPointerControllerPolicyInterface::loadAdditionalMouseResources( - std::map<int32_t, SpriteIcon>* outResources, - std::map<int32_t, PointerAnimation>* outAnimationResources, - int32_t) { + std::map<PointerIconStyle, SpriteIcon>* outResources, + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t) { SpriteIcon icon; PointerAnimation anim; // CURSOR_TYPE_ADDITIONAL doesn't have animation resource. int32_t cursorType = CURSOR_TYPE_ADDITIONAL; loadPointerIconForType(&icon, cursorType); - (*outResources)[cursorType] = icon; + (*outResources)[static_cast<PointerIconStyle>(cursorType)] = icon; // CURSOR_TYPE_ADDITIONAL_ANIM has animation resource. cursorType = CURSOR_TYPE_ADDITIONAL_ANIM; loadPointerIconForType(&icon, cursorType); anim.animationFrames.push_back(icon); anim.durationPerFrame = 10; - (*outResources)[cursorType] = icon; - (*outAnimationResources)[cursorType] = anim; + (*outResources)[static_cast<PointerIconStyle>(cursorType)] = icon; + (*outAnimationResources)[static_cast<PointerIconStyle>(cursorType)] = anim; additionalMouseResourcesLoaded = true; } -int32_t MockPointerControllerPolicyInterface::getDefaultPointerIconId() { - return CURSOR_TYPE_DEFAULT; +PointerIconStyle MockPointerControllerPolicyInterface::getDefaultPointerIconId() { + return static_cast<PointerIconStyle>(CURSOR_TYPE_DEFAULT); } -int32_t MockPointerControllerPolicyInterface::getCustomPointerIconId() { - return CURSOR_TYPE_CUSTOM; +PointerIconStyle MockPointerControllerPolicyInterface::getCustomPointerIconId() { + return static_cast<PointerIconStyle>(CURSOR_TYPE_CUSTOM); } bool MockPointerControllerPolicyInterface::allResourcesAreLoaded() { @@ -124,7 +125,7 @@ bool MockPointerControllerPolicyInterface::noResourcesAreLoaded() { } void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* icon, int32_t type) { - icon->style = type; + icon->style = static_cast<PointerIconStyle>(type); std::pair<float, float> hotSpot = getHotSpotCoordinatesForType(type); icon->hotSpotX = hotSpot.first; icon->hotSpotY = hotSpot.second; @@ -205,11 +206,11 @@ TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) { std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT); EXPECT_CALL(*mPointerSprite, setVisible(true)); EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); - EXPECT_CALL(*mPointerSprite, setIcon( - AllOf( - Field(&SpriteIcon::style, CURSOR_TYPE_DEFAULT), - Field(&SpriteIcon::hotSpotX, hotspot.first), - Field(&SpriteIcon::hotSpotY, hotspot.second)))); + EXPECT_CALL(*mPointerSprite, + setIcon(AllOf(Field(&SpriteIcon::style, + static_cast<PointerIconStyle>(CURSOR_TYPE_DEFAULT)), + Field(&SpriteIcon::hotSpotX, hotspot.first), + Field(&SpriteIcon::hotSpotY, hotspot.second)))); mPointerController->reloadPointerResources(); } @@ -222,12 +223,11 @@ TEST_F(PointerControllerTest, updatePointerIcon) { std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type); EXPECT_CALL(*mPointerSprite, setVisible(true)); EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); - EXPECT_CALL(*mPointerSprite, setIcon( - AllOf( - Field(&SpriteIcon::style, type), - Field(&SpriteIcon::hotSpotX, hotspot.first), - Field(&SpriteIcon::hotSpotY, hotspot.second)))); - mPointerController->updatePointerIcon(type); + EXPECT_CALL(*mPointerSprite, + setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(type)), + Field(&SpriteIcon::hotSpotX, hotspot.first), + Field(&SpriteIcon::hotSpotY, hotspot.second)))); + mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type)); } TEST_F(PointerControllerTest, setCustomPointerIcon) { @@ -239,17 +239,16 @@ TEST_F(PointerControllerTest, setCustomPointerIcon) { float hotSpotY = 20; SpriteIcon icon; - icon.style = style; + icon.style = static_cast<PointerIconStyle>(style); icon.hotSpotX = hotSpotX; icon.hotSpotY = hotSpotY; EXPECT_CALL(*mPointerSprite, setVisible(true)); EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); - EXPECT_CALL(*mPointerSprite, setIcon( - AllOf( - Field(&SpriteIcon::style, style), - Field(&SpriteIcon::hotSpotX, hotSpotX), - Field(&SpriteIcon::hotSpotY, hotSpotY)))); + EXPECT_CALL(*mPointerSprite, + setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(style)), + Field(&SpriteIcon::hotSpotX, hotSpotX), + Field(&SpriteIcon::hotSpotY, hotSpotY)))); mPointerController->setCustomPointerIcon(icon); } |