diff options
Diffstat (limited to 'libs')
782 files changed, 27402 insertions, 10471 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index 76e0e1eb7a95..c3d8f9a99d79 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -16,12 +16,14 @@ package androidx.window.extensions; +import android.app.ActivityTaskManager; import android.app.ActivityThread; import android.app.Application; import android.content.Context; import android.window.TaskFragmentOrganizer; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.RawFoldingFeatureProducer; import androidx.window.extensions.area.WindowAreaComponent; @@ -48,7 +50,7 @@ public class WindowExtensionsImpl implements WindowExtensions { // TODO(b/241126279) Introduce constants to better version functionality @Override public int getVendorApiLevel() { - return 3; + return 4; } @NonNull @@ -111,9 +113,13 @@ public class WindowExtensionsImpl implements WindowExtensions { * {@link WindowExtensions#getWindowLayoutComponent()}. * @return {@link ActivityEmbeddingComponent} OEM implementation. */ - @NonNull + @Nullable public ActivityEmbeddingComponent getActivityEmbeddingComponent() { if (mSplitController == null) { + if (!ActivityTaskManager.supportsMultiWindow(getApplication())) { + // Disable AE for device that doesn't support multi window. + return null; + } synchronized (mLock) { if (mSplitController == null) { mSplitController = new SplitController( diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index 1e6e50359cf3..15d14e87fcf6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -213,9 +213,6 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, if (mRearDisplayStateRequest != null || isRearDisplayActive()) { mRearDisplayStateRequest = null; mDeviceStateManager.cancelStateRequest(); - } else { - throw new IllegalStateException( - "Unable to cancel a rear display session as there is no active session"); } } } @@ -432,10 +429,6 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, synchronized (mLock) { if (mRearDisplayPresentationController != null) { mDeviceStateManager.cancelStateRequest(); - } else { - throw new IllegalStateException( - "Unable to cancel a rear display presentation session as there is no " - + "active session"); } } } @@ -518,8 +511,11 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, return WindowAreaComponent.STATUS_UNSUPPORTED; } - if (mCurrentDeviceState == mConcurrentDisplayState - || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState) + if (mCurrentDeviceState == mConcurrentDisplayState) { + return WindowAreaComponent.STATUS_ACTIVE; + } + + if (!ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState) || isDeviceFolded()) { return WindowAreaComponent.STATUS_UNAVAILABLE; } 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 d94e8e426c4b..4d73c20fe39f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -17,7 +17,9 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION; import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; @@ -340,6 +342,20 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.deleteTaskFragment(fragmentToken); } + void reorderTaskFragmentToFront(@NonNull WindowContainerTransaction wct, + @NonNull IBinder fragmentToken) { + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REORDER_TO_FRONT).build(); + wct.addTaskFragmentOperation(fragmentToken, operation); + } + + void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct, + @NonNull IBinder fragmentToken, boolean isolatedNav) { + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_ISOLATED_NAVIGATION).setIsolatedNav(isolatedNav).build(); + wct.addTaskFragmentOperation(fragmentToken, operation); + } + void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) { mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java index 18497ad249ee..08b7bb89d10c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -25,6 +25,7 @@ import android.window.TaskFragmentParentInfo; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.window.extensions.core.util.function.Function; /** @@ -32,7 +33,7 @@ import androidx.window.extensions.core.util.function.Function; */ class SplitContainer { @NonNull - private final TaskFragmentContainer mPrimaryContainer; + private TaskFragmentContainer mPrimaryContainer; @NonNull private final TaskFragmentContainer mSecondaryContainer; @NonNull @@ -46,17 +47,35 @@ class SplitContainer { @NonNull private final IBinder mToken; + /** + * Whether the selection of which container is primary can be changed at runtime. Runtime + * updates is currently possible only for {@link SplitPinContainer} + * + * @see SplitPinContainer + */ + private final boolean mIsPrimaryContainerMutable; + SplitContainer(@NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) { + this(primaryContainer, primaryActivity, secondaryContainer, splitRule, splitAttributes, + false /* isPrimaryContainerMutable */); + } + + SplitContainer(@NonNull TaskFragmentContainer primaryContainer, + @NonNull Activity primaryActivity, + @NonNull TaskFragmentContainer secondaryContainer, + @NonNull SplitRule splitRule, + @NonNull SplitAttributes splitAttributes, boolean isPrimaryContainerMutable) { mPrimaryContainer = primaryContainer; mSecondaryContainer = secondaryContainer; mSplitRule = splitRule; mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes(); mCurrentSplitAttributes = splitAttributes; mToken = new Binder("SplitContainer"); + mIsPrimaryContainerMutable = isPrimaryContainerMutable; if (shouldFinishPrimaryWithSecondary(splitRule)) { if (mPrimaryContainer.getRunningActivityCount() == 1 @@ -74,6 +93,13 @@ class SplitContainer { } } + void setPrimaryContainer(@NonNull TaskFragmentContainer primaryContainer) { + if (!mIsPrimaryContainerMutable) { + throw new IllegalStateException("Cannot update primary TaskFragmentContainer"); + } + mPrimaryContainer = primaryContainer; + } + @NonNull TaskFragmentContainer getPrimaryContainer() { return mPrimaryContainer; @@ -161,10 +187,21 @@ class SplitContainer { return (mSplitRule instanceof SplitPlaceholderRule); } - @NonNull - SplitInfo toSplitInfo() { - return new SplitInfo(mPrimaryContainer.toActivityStack(), - mSecondaryContainer.toActivityStack(), mCurrentSplitAttributes, mToken); + /** + * Returns the SplitInfo representing this container. + * + * @return the SplitInfo representing this container if the underlying TaskFragmentContainers + * are stable, or {@code null} if any TaskFragmentContainer is in an intermediate state. + */ + @Nullable + SplitInfo toSplitInfoIfStable() { + final ActivityStack primaryActivityStack = mPrimaryContainer.toActivityStackIfStable(); + final ActivityStack secondaryActivityStack = mSecondaryContainer.toActivityStackIfStable(); + if (primaryActivityStack == null || secondaryActivityStack == null) { + return null; + } + return new SplitInfo(primaryActivityStack, secondaryActivityStack, + mCurrentSplitAttributes, mToken); } static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) { 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 89f4890c254e..a5ee19e2d068 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -213,6 +213,93 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override + public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) { + synchronized (mLock) { + final TaskContainer task = getTaskContainer(taskId); + if (task == null) { + Log.e(TAG, "Cannot find the task for id: " + taskId); + return false; + } + + final TaskFragmentContainer topContainer = + task.getTopNonFinishingTaskFragmentContainer(); + // Cannot pin the TaskFragment if no other TaskFragment behind it. + if (topContainer == null || task.indexOf(topContainer) <= 0) { + Log.w(TAG, "Cannot find an ActivityStack to pin or split"); + return false; + } + // Abort if the top container is already pinned. + if (task.getSplitPinContainer() != null) { + Log.w(TAG, "There is already a pinned ActivityStack."); + return false; + } + + // Find a valid adjacent TaskFragmentContainer + final TaskFragmentContainer primaryContainer = + task.getNonFinishingTaskFragmentContainerBelow(topContainer); + if (primaryContainer == null) { + Log.w(TAG, "Cannot find another ActivityStack to split"); + return false; + } + + // Abort if no space to split. + final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes( + task.getTaskProperties(), splitPinRule, + splitPinRule.getDefaultSplitAttributes(), + getActivitiesMinDimensionsPair(primaryContainer.getTopNonFinishingActivity(), + topContainer.getTopNonFinishingActivity())); + if (!SplitPresenter.shouldShowSplit(calculatedSplitAttributes)) { + Log.w(TAG, "No space to split, abort pinning top ActivityStack."); + return false; + } + + // Registers a Split + final SplitPinContainer splitPinContainer = new SplitPinContainer(primaryContainer, + topContainer, splitPinRule, calculatedSplitAttributes); + task.addSplitContainer(splitPinContainer); + + // Updates the Split + final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); + final WindowContainerTransaction wct = transactionRecord.getTransaction(); + mPresenter.updateSplitContainer(splitPinContainer, wct); + transactionRecord.apply(false /* shouldApplyIndependently */); + updateCallbackIfNecessary(); + return true; + } + } + + @Override + public void unpinTopActivityStack(int taskId){ + synchronized (mLock) { + final TaskContainer task = getTaskContainer(taskId); + if (task == null) { + Log.e(TAG, "Cannot find the task to unpin, id: " + taskId); + return; + } + + final SplitPinContainer splitPinContainer = task.getSplitPinContainer(); + if (splitPinContainer == null) { + Log.e(TAG, "No ActivityStack is pinned."); + return; + } + + // Remove the SplitPinContainer from the task. + final TaskFragmentContainer containerToUnpin = + splitPinContainer.getSecondaryContainer(); + task.removeSplitPinContainer(); + + // Resets the isolated navigation and updates the container. + final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); + final WindowContainerTransaction wct = transactionRecord.getTransaction(); + mPresenter.setTaskFragmentIsolatedNavigation(wct, + containerToUnpin.getTaskFragmentToken(), false /* isolated */); + updateContainer(wct, containerToUnpin); + transactionRecord.apply(false /* shouldApplyIndependently */); + updateCallbackIfNecessary(); + } + } + + @Override public void setSplitAttributesCalculator( @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) { synchronized (mLock) { @@ -308,7 +395,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen forAllTaskContainers(taskContainer -> { synchronized (mLock) { - final List<TaskFragmentContainer> containers = taskContainer.mContainers; + final List<TaskFragmentContainer> containers = + taskContainer.getTaskFragmentContainers(); // Clean up the TaskFragmentContainers by the z-order from the lowest. for (int i = 0; i < containers.size(); i++) { final TaskFragmentContainer container = containers.get(i); @@ -611,8 +699,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull TaskContainer taskContainer) { // Update all TaskFragments in the Task. Make a copy of the list since some may be // removed on updating. - final List<TaskFragmentContainer> containers = - new ArrayList<>(taskContainer.mContainers); + final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); for (int i = containers.size() - 1; i >= 0; i--) { final TaskFragmentContainer container = containers.get(i); // Wait until onTaskFragmentAppeared to update new container. @@ -672,7 +759,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (targetContainer == null) { // When there is no embedding rule matched, try to place it in the top container // like a normal launch. - targetContainer = taskContainer.getTopTaskFragmentContainer(); + targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer(); } if (targetContainer == null) { return; @@ -777,7 +864,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } - if (!isOnReparent && getContainerWithActivity(activity) == null + final TaskFragmentContainer container = getContainerWithActivity(activity); + if (!isOnReparent && container == null && getTaskFragmentTokenFromActivityClientRecord(activity) != null) { // We can't find the new launched activity in any recorded container, but it is // currently placed in an embedded TaskFragment. This can happen in two cases: @@ -789,10 +877,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } - final TaskFragmentContainer container = getContainerWithActivity(activity); - if (!isOnReparent && container != null - && container.getTaskContainer().getTopTaskFragmentContainer() != container) { - // Do not resolve if the launched activity is not the top-most container in the Task. + // Skip resolving if the activity is on a pinned TaskFragmentContainer. + // TODO(b/243518738): skip resolving for overlay container. + if (container != null) { + final TaskContainer taskContainer = container.getTaskContainer(); + if (taskContainer.isTaskFragmentContainerPinned(container)) { + return true; + } + } + + final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null; + if (!isOnReparent && taskContainer != null + && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */) + != container) { + // Do not resolve if the launched activity is not the top-most container (excludes + // the pinned container) in the Task. return true; } @@ -888,7 +987,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskContainer == null) { return; } - final TaskFragmentContainer targetContainer = taskContainer.getTopTaskFragmentContainer(); + final TaskFragmentContainer targetContainer = + taskContainer.getTopNonFinishingTaskFragmentContainer(); if (targetContainer == null) { return; } @@ -1188,6 +1288,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) { + // Skip resolving if started from pinned TaskFragmentContainer. + // TODO(b/243518738): skip resolving for overlay container. + if (launchingActivity != null) { + final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity( + launchingActivity); + final TaskContainer taskContainer = + taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null; + if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned( + taskFragmentContainer)) { + return null; + } + } + /* * We will check the following to see if there is any embedding rule matched: * 1. Whether the new activity intent should always expand. @@ -1213,11 +1326,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // 3. Whether the top activity (if any) should be split with the new activity intent. final TaskContainer taskContainer = getTaskContainer(taskId); - if (taskContainer == null || taskContainer.getTopTaskFragmentContainer() == null) { + if (taskContainer == null + || taskContainer.getTopNonFinishingTaskFragmentContainer() == null) { // There is no other activity in the Task to check split with. return null; } - final TaskFragmentContainer topContainer = taskContainer.getTopTaskFragmentContainer(); + final TaskFragmentContainer topContainer = + taskContainer.getTopNonFinishingTaskFragmentContainer(); final Activity topActivity = topContainer.getTopNonFinishingActivity(); if (topActivity != null && topActivity != launchingActivity) { final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct, @@ -1331,7 +1446,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Check pending appeared activity first because there can be a delay for the server // update. for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers; + final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) + .getTaskFragmentContainers(); for (int j = containers.size() - 1; j >= 0; j--) { final TaskFragmentContainer container = containers.get(j); if (container.hasPendingAppearedActivity(activityToken)) { @@ -1342,7 +1458,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Check appeared activity if there is no such pending appeared activity. for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers; + final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) + .getTaskFragmentContainers(); for (int j = containers.size() - 1; j >= 0; j--) { final TaskFragmentContainer container = containers.get(j); if (container.hasAppearedActivity(activityToken)) { @@ -1418,7 +1535,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { removeExistingSecondaryContainers(wct, primaryContainer); } - primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer); + primaryContainer.getTaskContainer().addSplitContainer(splitContainer); } /** Cleanups all the dependencies when the TaskFragment is entering PIP. */ @@ -1430,8 +1547,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } final List<SplitContainer> splitsToRemove = new ArrayList<>(); + final List<SplitContainer> splitContainers = taskContainer.getSplitContainers(); final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>(); - for (SplitContainer splitContainer : taskContainer.mSplitContainers) { + for (SplitContainer splitContainer : splitContainers) { if (splitContainer.getPrimaryContainer() != container && splitContainer.getSecondaryContainer() != container) { continue; @@ -1449,7 +1567,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } container.resetDependencies(); - taskContainer.mSplitContainers.removeAll(splitsToRemove); + taskContainer.removeSplitContainers(splitsToRemove); // If there is any TaskFragment split with the PIP TaskFragment, update their presentations // since the split is dismissed. // We don't want to close any of them even if they are dependencies of the PIP TaskFragment. @@ -1471,7 +1589,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen void removeContainers(@NonNull TaskContainer taskContainer, @NonNull List<TaskFragmentContainer> containers) { // Remove all split containers that included this one - taskContainer.mContainers.removeAll(containers); + taskContainer.removeTaskFragmentContainers(containers); // Marked as a pending removal which will be removed after it is actually removed on the // server side (#onTaskFragmentVanished). // In this way, we can keep track of the Task bounds until we no longer have any @@ -1481,7 +1599,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Cleanup any split references. final List<SplitContainer> containersToRemove = new ArrayList<>(); - for (SplitContainer splitContainer : taskContainer.mSplitContainers) { + final List<SplitContainer> splitContainers = taskContainer.getSplitContainers(); + for (SplitContainer splitContainer : splitContainers) { if (containersToRemove.contains(splitContainer)) { // Don't need to check because it has been in the remove list. continue; @@ -1492,10 +1611,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen containersToRemove.add(splitContainer); } } - taskContainer.mSplitContainers.removeAll(containersToRemove); + taskContainer.removeSplitContainers(containersToRemove); // Cleanup any dependent references. - for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) { + final List<TaskFragmentContainer> taskFragmentContainers = + taskContainer.getTaskFragmentContainers(); + for (TaskFragmentContainer containerToUpdate : taskFragmentContainers) { containerToUpdate.removeContainersToFinishOnExit(containers); } } @@ -1520,6 +1641,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } + // If the secondary container is pinned, it should not be removed. + final SplitContainer activeContainer = + getActiveSplitForContainer(existingSplitContainer.getSecondaryContainer()); + if (activeContainer instanceof SplitPinContainer) { + return; + } + existingSplitContainer.getSecondaryContainer().finish( false /* shouldFinishDependent */, mPresenter, wct, this); } @@ -1534,8 +1662,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskContainer == null) { return null; } - for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) { - final TaskFragmentContainer container = taskContainer.mContainers.get(i); + final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); + for (int i = containers.size() - 1; i >= 0; i--) { + final TaskFragmentContainer container = containers.get(i); if (!container.isFinished() && (container.getRunningActivityCount() > 0 // We may be waiting for the top TaskFragment to become non-empty after // creation. In that case, we don't want to treat the TaskFragment below it as @@ -1560,6 +1689,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // background. return; } + if (launchPlaceholderIfNecessary(wct, container)) { // Placeholder was launched, the positions will be updated when the activity is added // to the secondary container. @@ -1572,7 +1702,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // If the info is not available yet the task fragment will be expanded when it's ready return; } - SplitContainer splitContainer = getActiveSplitForContainer(container); + final SplitContainer splitContainer = getActiveSplitForContainer(container); if (splitContainer == null) { return; } @@ -1629,7 +1759,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** Whether the given split is the topmost split in the Task. */ private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) { final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer() - .getTaskContainer().mSplitContainers; + .getTaskContainer().getSplitContainers(); return splitContainer == splitContainers.get(splitContainers.size() - 1); } @@ -1641,7 +1771,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (container == null) { return null; } - final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers; + final List<SplitContainer> splitContainers = + container.getTaskContainer().getSplitContainers(); if (splitContainers.isEmpty()) { return null; } @@ -1665,7 +1796,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer secondContainer) { final List<SplitContainer> splitContainers = firstContainer.getTaskContainer() - .mSplitContainers; + .getSplitContainers(); for (int i = splitContainers.size() - 1; i >= 0; i--) { final SplitContainer splitContainer = splitContainers.get(i); final TaskFragmentContainer primary = splitContainer.getPrimaryContainer(); @@ -1755,6 +1886,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Don't launch placeholder for primary split container. return false; } + if (splitContainer instanceof SplitPinContainer) { + // Don't launch placeholder if pinned + return false; + } return true; } @@ -1837,8 +1972,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (mEmbeddingCallback == null || !readyToReportToClient()) { return; } - final List<SplitInfo> currentSplitStates = getActiveSplitStates(); - if (mLastReportedSplitStates.equals(currentSplitStates)) { + final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable(); + if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) { return; } mLastReportedSplitStates.clear(); @@ -1848,13 +1983,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Returns a list of descriptors for currently active split states. + * + * @return a list of descriptors for currently active split states if all the containers are in + * a stable state, or {@code null} otherwise. */ @GuardedBy("mLock") - @NonNull - private List<SplitInfo> getActiveSplitStates() { + @Nullable + private List<SplitInfo> getActiveSplitStatesIfStable() { final List<SplitInfo> splitStates = new ArrayList<>(); for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - mTaskContainers.valueAt(i).getSplitStates(splitStates); + final List<SplitInfo> taskSplitStates = + mTaskContainers.valueAt(i).getSplitStatesIfStable(); + if (taskSplitStates == null) { + return null; + } + splitStates.addAll(taskSplitStates); } return splitStates; } @@ -1930,7 +2073,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers; + final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) + .getTaskFragmentContainers(); for (TaskFragmentContainer container : containers) { if (container.getTaskFragmentToken().equals(fragmentToken)) { return container; @@ -1945,7 +2089,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") SplitContainer getSplitContainer(@NonNull IBinder token) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers; + final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers(); for (SplitContainer container : containers) { if (container.getToken().equals(token)) { return container; @@ -2000,8 +2144,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns {@code true} if an Activity with the provided component name should always be * expanded to occupy full task bounds. Such activity must not be put in a split. */ + @VisibleForTesting @GuardedBy("mLock") - private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) { + boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) { for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof ActivityRule)) { continue; @@ -2091,7 +2236,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } for (int i = mTaskContainers.size() - 1; i >= 0; i--) { final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) - .mContainers; + .getTaskFragmentContainers(); for (int j = containers.size() - 1; j >= 0; j--) { final TaskFragmentContainer container = containers.get(j); if (!container.hasActivity(activityToken) diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java new file mode 100644 index 000000000000..03c77a089012 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java @@ -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 androidx.window.extensions.embedding; + +import androidx.annotation.NonNull; + +/** + * Client-side descriptor of a split that holds two containers while the secondary + * container is pinned on top of the Task and the primary container is the container that is + * currently below the secondary container. The primary container could be updated to + * another container whenever the existing primary container is removed or no longer + * be the container that's right behind the secondary container. + */ +class SplitPinContainer extends SplitContainer { + + SplitPinContainer(@NonNull TaskFragmentContainer primaryContainer, + @NonNull TaskFragmentContainer secondaryContainer, + @NonNull SplitPinRule splitPinRule, + @NonNull SplitAttributes splitAttributes) { + super(primaryContainer, primaryContainer.getTopNonFinishingActivity(), secondaryContainer, + splitPinRule, splitAttributes, true /* isPrimaryContainerMutable */); + } + + @Override + public String toString() { + return "SplitPinContainer{" + + " primaryContainer=" + getPrimaryContainer() + + " secondaryContainer=" + getSecondaryContainer() + + " splitPinRule=" + getSplitRule() + + " splitAttributes" + getCurrentSplitAttributes() + + "}"; + } +} 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 53d39d9fa28e..d894487fafb6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -336,10 +336,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // value. final SplitRule rule = splitContainer.getSplitRule(); final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer(); - final Activity activity = primaryContainer.getTopNonFinishingActivity(); - if (activity == null) { - return; - } final TaskContainer taskContainer = splitContainer.getTaskContainer(); final TaskProperties taskProperties = taskContainer.getTaskProperties(); final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes(); @@ -386,6 +382,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken(), splitRule, isStacked); + + // Setting isolated navigation and clear non-sticky pinned container if needed. + final SplitPinRule splitPinRule = + splitRule instanceof SplitPinRule ? (SplitPinRule) splitRule : null; + if (splitPinRule == null) { + return; + } + + setTaskFragmentIsolatedNavigation(wct, secondaryContainer.getTaskFragmentToken(), + !isStacked /* isolatedNav */); + if (isStacked && !splitPinRule.isSticky()) { + secondaryContainer.getTaskContainer().removeSplitPinContainer(); + } } /** @@ -424,6 +433,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { container.setLastRequestedBounds(fragmentOptions.getInitialRelativeBounds()); container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode()); super.createTaskFragment(wct, fragmentOptions); + + // Reorders the pinned TaskFragment to front to ensure it is the front-most TaskFragment. + final SplitPinContainer pinnedContainer = + container.getTaskContainer().getSplitPinContainer(); + if (pinnedContainer != null) { + reorderTaskFragmentToFront(wct, + pinnedContainer.getSecondaryContainer().getTaskFragmentToken()); + } } @Override @@ -634,7 +651,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { if (minDimensionsPair == null) { return splitAttributes; } - final FoldingFeature foldingFeature = getFoldingFeature(taskProperties); + final FoldingFeature foldingFeature = getFoldingFeatureForHingeType( + taskProperties, splitAttributes); final Configuration taskConfiguration = taskProperties.getConfiguration(); final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes, foldingFeature); @@ -709,7 +727,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { Rect getRelBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties, @NonNull SplitAttributes splitAttributes) { final Configuration taskConfiguration = taskProperties.getConfiguration(); - final FoldingFeature foldingFeature = getFoldingFeature(taskProperties); + final FoldingFeature foldingFeature = getFoldingFeatureForHingeType( + taskProperties, splitAttributes); if (!shouldShowSplit(splitAttributes)) { return new Rect(); } @@ -916,6 +935,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } @Nullable + private FoldingFeature getFoldingFeatureForHingeType( + @NonNull TaskProperties taskProperties, + @NonNull SplitAttributes splitAttributes) { + SplitType splitType = splitAttributes.getSplitType(); + if (!(splitType instanceof HingeSplitType)) { + return null; + } + return getFoldingFeature(taskProperties); + } + + @Nullable @VisibleForTesting FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) { final int displayId = taskProperties.getDisplayId(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 4b15bb187035..9a0769a82d99 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -51,11 +51,15 @@ class TaskContainer { /** Active TaskFragments in this Task. */ @NonNull - final List<TaskFragmentContainer> mContainers = new ArrayList<>(); + private final List<TaskFragmentContainer> mContainers = new ArrayList<>(); /** Active split pairs in this Task. */ @NonNull - final List<SplitContainer> mSplitContainers = new ArrayList<>(); + private final List<SplitContainer> mSplitContainers = new ArrayList<>(); + + /** Active pin split pair in this Task. */ + @Nullable + private SplitPinContainer mSplitPinContainer; @NonNull private final Configuration mConfiguration; @@ -174,11 +178,36 @@ class TaskContainer { } @Nullable - TaskFragmentContainer getTopTaskFragmentContainer() { - if (mContainers.isEmpty()) { - return null; + TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() { + return getTopNonFinishingTaskFragmentContainer(true /* includePin */); + } + + @Nullable + TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) { + for (int i = mContainers.size() - 1; i >= 0; i--) { + final TaskFragmentContainer container = mContainers.get(i); + if (!includePin && isTaskFragmentContainerPinned(container)) { + continue; + } + if (!container.isFinished()) { + return container; + } } - return mContainers.get(mContainers.size() - 1); + return null; + } + + /** Gets a non-finishing container below the given one. */ + @Nullable + TaskFragmentContainer getNonFinishingTaskFragmentContainerBelow( + @NonNull TaskFragmentContainer current) { + final int index = mContainers.indexOf(current); + for (int i = index - 1; i >= 0; i--) { + final TaskFragmentContainer container = mContainers.get(i); + if (!container.isFinished()) { + return container; + } + } + return null; } @Nullable @@ -207,11 +236,146 @@ class TaskContainer { return false; } - /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */ - void getSplitStates(@NonNull List<SplitInfo> outSplitStates) { + /** + * Returns a list of {@link SplitContainer}. Do not modify the containers directly on the + * returned list. Use {@link #addSplitContainer} or {@link #removeSplitContainers} instead. + */ + @NonNull + List<SplitContainer> getSplitContainers() { + return mSplitContainers; + } + + void addSplitContainer(@NonNull SplitContainer splitContainer) { + if (splitContainer instanceof SplitPinContainer) { + mSplitPinContainer = (SplitPinContainer) splitContainer; + mSplitContainers.add(splitContainer); + return; + } + + // Keeps the SplitPinContainer on the top of the list. + mSplitContainers.remove(mSplitPinContainer); + mSplitContainers.add(splitContainer); + if (mSplitPinContainer != null) { + mSplitContainers.add(mSplitPinContainer); + } + } + + void removeSplitContainers(@NonNull List<SplitContainer> containers) { + mSplitContainers.removeAll(containers); + } + + void removeSplitPinContainer() { + if (mSplitPinContainer == null) { + return; + } + + final TaskFragmentContainer primaryContainer = mSplitPinContainer.getPrimaryContainer(); + final TaskFragmentContainer secondaryContainer = mSplitPinContainer.getSecondaryContainer(); + mSplitContainers.remove(mSplitPinContainer); + mSplitPinContainer = null; + + // Remove the other SplitContainers that contains the unpinned container (unless it + // is the current top-most split-pair), since the state are no longer valid. + final List<SplitContainer> splitsToRemove = new ArrayList<>(); + for (SplitContainer splitContainer : mSplitContainers) { + if (splitContainer.getSecondaryContainer().equals(secondaryContainer) + && !splitContainer.getPrimaryContainer().equals(primaryContainer)) { + splitsToRemove.add(splitContainer); + } + } + removeSplitContainers(splitsToRemove); + } + + @Nullable + SplitPinContainer getSplitPinContainer() { + return mSplitPinContainer; + } + + boolean isTaskFragmentContainerPinned(@NonNull TaskFragmentContainer taskFragmentContainer) { + return mSplitPinContainer != null + && mSplitPinContainer.getSecondaryContainer() == taskFragmentContainer; + } + + void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) { + mContainers.add(taskFragmentContainer); + onTaskFragmentContainerUpdated(); + } + + void addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer) { + mContainers.add(index, taskFragmentContainer); + onTaskFragmentContainerUpdated(); + } + + void removeTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) { + mContainers.remove(taskFragmentContainer); + onTaskFragmentContainerUpdated(); + } + + void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) { + mContainers.removeAll(taskFragmentContainer); + onTaskFragmentContainerUpdated(); + } + + void clearTaskFragmentContainer() { + mContainers.clear(); + onTaskFragmentContainerUpdated(); + } + + /** + * Returns a list of {@link TaskFragmentContainer}. Do not modify the containers directly on + * the returned list. Use {@link #addTaskFragmentContainer}, + * {@link #removeTaskFragmentContainer} or other related methods instead. + */ + @NonNull + List<TaskFragmentContainer> getTaskFragmentContainers() { + return mContainers; + } + + private void onTaskFragmentContainerUpdated() { + if (mSplitPinContainer == null) { + return; + } + + final TaskFragmentContainer pinnedContainer = mSplitPinContainer.getSecondaryContainer(); + final int pinnedContainerIndex = mContainers.indexOf(pinnedContainer); + if (pinnedContainerIndex <= 0) { + removeSplitPinContainer(); + return; + } + + // Ensure the pinned container is top-most. + if (pinnedContainerIndex != mContainers.size() - 1) { + mContainers.remove(pinnedContainer); + mContainers.add(pinnedContainer); + } + + // Update the primary container adjacent to the pinned container if needed. + final TaskFragmentContainer adjacentContainer = + getNonFinishingTaskFragmentContainerBelow(pinnedContainer); + if (adjacentContainer == null) { + removeSplitPinContainer(); + } else if (mSplitPinContainer.getPrimaryContainer() != adjacentContainer) { + mSplitPinContainer.setPrimaryContainer(adjacentContainer); + } + } + + /** + * Gets the descriptors of split states in this Task. + * + * @return a list of {@code SplitInfo} if all the SplitContainers are stable, or {@code null} if + * any SplitContainer is in an intermediate state. + */ + @Nullable + List<SplitInfo> getSplitStatesIfStable() { + final List<SplitInfo> splitStates = new ArrayList<>(); for (SplitContainer container : mSplitContainers) { - outSplitStates.add(container.toSplitInfo()); + final SplitInfo splitInfo = container.toSplitInfoIfStable(); + if (splitInfo == null) { + return null; + } + splitStates.add(splitInfo); } + return splitStates; } /** A wrapper class which contains the information of {@link TaskContainer} */ diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 60be9d16d749..0a694b5c3b64 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -180,23 +180,25 @@ class TaskFragmentContainer { throw new IllegalArgumentException( "pairedPrimaryContainer must be in the same Task"); } - final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer); - taskContainer.mContainers.add(primaryIndex + 1, this); + final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer); + taskContainer.addTaskFragmentContainer(primaryIndex + 1, this); } else if (pendingAppearedActivity != null) { // The TaskFragment will be positioned right above the pending appeared Activity. If any // existing TaskFragment is empty with pending Intent, it is likely that the Activity of // the pending Intent hasn't been created yet, so the new Activity should be below the // empty TaskFragment. - int i = taskContainer.mContainers.size() - 1; + final List<TaskFragmentContainer> containers = + taskContainer.getTaskFragmentContainers(); + int i = containers.size() - 1; for (; i >= 0; i--) { - final TaskFragmentContainer container = taskContainer.mContainers.get(i); + final TaskFragmentContainer container = containers.get(i); if (!container.isEmpty() || container.getPendingAppearedIntent() == null) { break; } } - taskContainer.mContainers.add(i + 1, this); + taskContainer.addTaskFragmentContainer(i + 1, this); } else { - taskContainer.mContainers.add(this); + taskContainer.addTaskFragmentContainer(this); } if (pendingAppearedActivity != null) { addPendingAppearedActivity(pendingAppearedActivity); @@ -215,6 +217,30 @@ class TaskFragmentContainer { /** List of non-finishing activities that belong to this container and live in this process. */ @NonNull List<Activity> collectNonFinishingActivities() { + final List<Activity> activities = collectNonFinishingActivities(false /* checkIfStable */); + if (activities == null) { + throw new IllegalStateException( + "Result activities should never be null when checkIfstable is false."); + } + return activities; + } + + /** + * Collects non-finishing activities that belong to this container and live in this process. + * + * @param checkIfStable if {@code true}, returns {@code null} when the container is in an + * intermediate state. + * @return List of non-finishing activities that belong to this container and live in this + * process, {@code null} if checkIfStable is {@code true} and the container is in an + * intermediate state. + */ + @Nullable + List<Activity> collectNonFinishingActivities(boolean checkIfStable) { + if (checkIfStable + && (mInfo == null || mInfo.isEmpty() || !mPendingAppearedActivities.isEmpty())) { + return null; + } + final List<Activity> allActivities = new ArrayList<>(); if (mInfo != null) { // Add activities reported from the server. @@ -222,6 +248,15 @@ class TaskFragmentContainer { final Activity activity = mController.getActivity(token); if (activity != null && !activity.isFinishing()) { allActivities.add(activity); + } else { + if (checkIfStable) { + // Return null except for a special case when the activity is started in + // background. + if (activity == null && !mTaskContainer.isVisible()) { + continue; + } + return null; + } } } } @@ -275,9 +310,19 @@ class TaskFragmentContainer { return false; } - @NonNull - ActivityStack toActivityStack() { - return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken); + /** + * Returns the ActivityStack representing this container. + * + * @return ActivityStack representing this container if it is in a stable state. {@code null} if + * in an intermediate state. + */ + @Nullable + ActivityStack toActivityStackIfStable() { + final List<Activity> activities = collectNonFinishingActivities(true /* checkIfStable */); + if (activities == null) { + return null; + } + return new ActivityStack(activities, isEmpty(), mToken); } /** Adds the activity that will be reparented to this container. */ diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index a45a8a183ac8..ba57b76020b4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -17,7 +17,6 @@ package androidx.window.extensions.layout; import static android.view.Display.DEFAULT_DISPLAY; - import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED; import static androidx.window.util.ExtensionHelper.isZero; @@ -98,14 +97,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { mTaskFragmentOrganizer = taskFragmentOrganizer; } - /** Registers to listen to {@link CommonFoldingFeature} changes */ - public void addFoldingStateChangedCallback( - java.util.function.Consumer<List<CommonFoldingFeature>> consumer) { - synchronized (mLock) { - mFoldingFeatureProducer.addDataChangedCallback(consumer); - } - } - /** * Adds a listener interested in receiving updates to {@link WindowLayoutInfo} * @@ -304,7 +295,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * {@link IllegalArgumentException} since this can cause negative UI effects down stream. * * @param context a proxy for the {@link android.view.Window} that contains the - * {@link DisplayFeature}. + * {@link DisplayFeature}. * @return a {@link List} of {@link DisplayFeature}s that are within the * {@link android.view.Window} of the {@link Activity} */ @@ -327,19 +318,45 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return features; } + // We will transform the feature bounds to the Activity window, so using the rotation + // from the same source (WindowConfiguration) to make sure they are synchronized. + final int rotation = windowConfiguration.getDisplayRotation(); + for (CommonFoldingFeature baseFeature : storedFeatures) { Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { continue; } Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, featureRect); + rotateRectToDisplayRotation(displayId, rotation, featureRect); transformToWindowSpaceRect(windowConfiguration, featureRect); - if (!isZero(featureRect)) { + if (isZero(featureRect)) { // TODO(b/228641877): Remove guarding when fixed. - features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); + continue; + } + if (featureRect.left != 0 && featureRect.top != 0) { + throw new IllegalArgumentException("Bounding rectangle must start at the top or " + + "left of the window. BaseFeatureRect: " + baseFeature.getRect() + + ", FeatureRect: " + featureRect + + ", WindowConfiguration: " + windowConfiguration); + + } + if (featureRect.left == 0 + && featureRect.width() != windowConfiguration.getBounds().width()) { + throw new IllegalArgumentException("Horizontal FoldingFeature must have full width." + + " BaseFeatureRect: " + baseFeature.getRect() + + ", FeatureRect: " + featureRect + + ", WindowConfiguration: " + windowConfiguration); + } + if (featureRect.top == 0 + && featureRect.height() != windowConfiguration.getBounds().height()) { + throw new IllegalArgumentException("Vertical FoldingFeature must have full height." + + " BaseFeatureRect: " + baseFeature.getRect() + + ", FeatureRect: " + featureRect + + ", WindowConfiguration: " + windowConfiguration); } + features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); } return features; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index 5bfb0ebdcaa8..a836e05b2d66 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -17,7 +17,6 @@ package androidx.window.sidecar; import static android.view.Display.DEFAULT_DISPLAY; - import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; @@ -120,10 +119,12 @@ class SampleSidecarImpl extends StubSidecar { } List<SidecarDisplayFeature> features = new ArrayList<>(); + final int rotation = activity.getResources().getConfiguration().windowConfiguration + .getDisplayRotation(); for (CommonFoldingFeature baseFeature : mStoredFeatures) { SidecarDisplayFeature feature = new SidecarDisplayFeature(); Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, featureRect); + rotateRectToDisplayRotation(displayId, rotation, featureRect); transformToWindowSpaceRect(activity, featureRect); feature.setRect(featureRect); feature.setType(baseFeature.getType()); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java index 9e2611f392a3..a08db7939eca 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java @@ -16,21 +16,22 @@ package androidx.window.util; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import android.annotation.SuppressLint; import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; +import android.util.RotationUtils; import android.view.DisplayInfo; import android.view.Surface; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.UiContext; +import androidx.annotation.VisibleForTesting; /** * Util class for both Sidecar and Extensions. @@ -44,47 +45,39 @@ public final class ExtensionHelper { /** * Rotates the input rectangle specified in default display orientation to the current display * rotation. + * + * @param displayId the display id. + * @param rotation the target rotation relative to the default display orientation. + * @param inOutRect the input/output Rect as specified in the default display orientation. */ - public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) { - DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance(); - DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId); - int rotation = displayInfo.rotation; + public static void rotateRectToDisplayRotation( + int displayId, @Surface.Rotation int rotation, @NonNull Rect inOutRect) { + final DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance(); + final DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId); - boolean isSideRotation = rotation == ROTATION_90 || rotation == ROTATION_270; - int displayWidth = isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth; - int displayHeight = isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight; - - inOutRect.intersect(0, 0, displayWidth, displayHeight); - - rotateBounds(inOutRect, displayWidth, displayHeight, rotation); + rotateRectToDisplayRotation(displayInfo, rotation, inOutRect); } - /** - * Rotates the input rectangle within parent bounds for a given delta. - */ - private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight, - @Surface.Rotation int delta) { - int origLeft = inOutRect.left; - switch (delta) { - case ROTATION_0: - return; - case ROTATION_90: - inOutRect.left = inOutRect.top; - inOutRect.top = parentWidth - inOutRect.right; - inOutRect.right = inOutRect.bottom; - inOutRect.bottom = parentWidth - origLeft; - return; - case ROTATION_180: - inOutRect.left = parentWidth - inOutRect.right; - inOutRect.right = parentWidth - origLeft; - return; - case ROTATION_270: - inOutRect.left = parentHeight - inOutRect.bottom; - inOutRect.bottom = inOutRect.right; - inOutRect.right = parentHeight - inOutRect.top; - inOutRect.top = origLeft; - return; - } + // We suppress the Lint error CheckResult for Rect#intersect because in case the displayInfo and + // folding features are out of sync, e.g. when a foldable devices is unfolding, it is acceptable + // to provide the original folding feature Rect even if they don't intersect. + @SuppressLint("RectIntersectReturnValueIgnored") + @VisibleForTesting + static void rotateRectToDisplayRotation(@NonNull DisplayInfo displayInfo, + @Surface.Rotation int rotation, @NonNull Rect inOutRect) { + // The inOutRect is specified in the default display orientation, so here we need to get + // the display width and height in the default orientation to perform the intersection and + // rotation. + final boolean isSideRotation = + displayInfo.rotation == ROTATION_90 || displayInfo.rotation == ROTATION_270; + final int baseDisplayWidth = + isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth; + final int baseDisplayHeight = + isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight; + + inOutRect.intersect(0, 0, baseDisplayWidth, baseDisplayHeight); + + RotationUtils.rotateBounds(inOutRect, baseDisplayWidth, baseDisplayHeight, rotation); } /** Transforms rectangle from absolute coordinate space to the window coordinate space. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java index d189ae2cf72e..9607b78bacf0 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java @@ -16,8 +16,10 @@ package androidx.window.extensions; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; +import android.app.ActivityTaskManager; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -29,7 +31,7 @@ import org.junit.Test; import org.junit.runner.RunWith; /** - * Test class for {@link WindowExtensionsTest}. + * Test class for {@link WindowExtensions}. * * Build/Install/Run: * atest WMJetpackUnitTests:WindowExtensionsTest @@ -52,7 +54,11 @@ public class WindowExtensionsTest { @Test public void testGetActivityEmbeddingComponent() { - assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull(); + if (ActivityTaskManager.supportsMultiWindow(getInstrumentation().getContext())) { + assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull(); + } else { + assertThat(mExtensions.getActivityEmbeddingComponent()).isNull(); + } } @Test 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 ff08782e8cd8..d440a3eb95de 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 @@ -183,23 +183,23 @@ public class SplitControllerTest { // tf2 has running activity so is active. final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class); doReturn(1).when(tf2).getRunningActivityCount(); - taskContainer.mContainers.add(tf2); + taskContainer.addTaskFragmentContainer(tf2); // tf3 is finished so is not active. final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class); doReturn(true).when(tf3).isFinished(); doReturn(false).when(tf3).isWaitingActivityAppear(); - taskContainer.mContainers.add(tf3); + taskContainer.addTaskFragmentContainer(tf3); mSplitController.mTaskContainers.put(TASK_ID, taskContainer); assertWithMessage("Must return tf2 because tf3 is not active.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2); - taskContainer.mContainers.remove(tf3); + taskContainer.removeTaskFragmentContainer(tf3); assertWithMessage("Must return tf2 because tf2 has running activity.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2); - taskContainer.mContainers.remove(tf2); + taskContainer.removeTaskFragmentContainer(tf2); assertWithMessage("Must return tf because we are waiting for tf1 to appear.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1); @@ -320,11 +320,11 @@ public class SplitControllerTest { doReturn(tf).when(splitContainer).getSecondaryContainer(); doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer(); doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule(); - final List<SplitContainer> splitContainers = - mSplitController.getTaskContainer(TASK_ID).mSplitContainers; - splitContainers.add(splitContainer); + final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); + taskContainer.addSplitContainer(splitContainer); // Add a mock SplitContainer on top of splitContainer - splitContainers.add(1, mock(SplitContainer.class)); + final SplitContainer splitContainer2 = mock(SplitContainer.class); + taskContainer.addSplitContainer(splitContainer2); mSplitController.updateContainer(mTransaction, tf); @@ -332,7 +332,9 @@ public class SplitControllerTest { // Verify if one or both containers in the top SplitContainer are finished, // dismissPlaceholder() won't be called. - splitContainers.remove(1); + final ArrayList<SplitContainer> splitContainersToRemove = new ArrayList<>(); + splitContainersToRemove.add(splitContainer2); + taskContainer.removeSplitContainers(splitContainersToRemove); doReturn(true).when(tf).isFinished(); mSplitController.updateContainer(mTransaction, tf); @@ -363,7 +365,8 @@ public class SplitControllerTest { final Activity r1 = createMockActivity(); addSplitTaskFragments(r0, r1); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); - final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0); + final TaskFragmentContainer taskFragmentContainer = + taskContainer.getTaskFragmentContainers().get(0); spyOn(taskContainer); // No update when the Task is invisible. @@ -377,7 +380,7 @@ public class SplitControllerTest { doReturn(true).when(taskContainer).isVisible(); mSplitController.updateContainer(mTransaction, taskFragmentContainer); - verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0), + verify(mSplitPresenter).updateSplitContainer(taskContainer.getSplitContainers().get(0), mTransaction); } @@ -592,6 +595,18 @@ public class SplitControllerTest { } @Test + public void testResolveStartActivityIntent_skipIfPinned() { + final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); + final TaskContainer taskContainer = container.getTaskContainer(); + spyOn(taskContainer); + final Intent intent = new Intent(); + setupSplitRule(mActivity, intent); + doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container); + assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent, + mActivity)); + } + + @Test public void testPlaceActivityInTopContainer() { mSplitController.placeActivityInTopContainer(mTransaction, mActivity); @@ -1041,6 +1056,29 @@ public class SplitControllerTest { } @Test + public void testResolveActivityToContainer_skipIfNonTopOrPinned() { + final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); + final Activity pinnedActivity = createMockActivity(); + final TaskFragmentContainer topContainer = mSplitController.newContainer(pinnedActivity, + TASK_ID); + final TaskContainer taskContainer = container.getTaskContainer(); + spyOn(taskContainer); + doReturn(container).when(taskContainer).getTopNonFinishingTaskFragmentContainer(false); + doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(topContainer); + + // No need to handle when the new launched activity is in a pinned TaskFragment. + assertTrue(mSplitController.resolveActivityToContainer(mTransaction, pinnedActivity, + false /* isOnReparent */)); + verify(mSplitController, never()).shouldExpand(any(), any()); + + // Should proceed to resolve if the new launched activity is in the next top TaskFragment + // (e.g. the top-most TaskFragment is pinned) + mSplitController.resolveActivityToContainer(mTransaction, mActivity, + false /* isOnReparent */); + verify(mSplitController).shouldExpand(any(), any()); + } + + @Test public void testGetPlaceholderOptions() { // Setup to make sure a transaction record is started. mTransactionManager.startNewTransaction(); @@ -1090,8 +1128,8 @@ public class SplitControllerTest { verify(mTransaction).finishActivity(mActivity.getActivityToken()); verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken()); verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken()); - assertTrue(taskContainer.mContainers.isEmpty()); - assertTrue(taskContainer.mSplitContainers.isEmpty()); + assertTrue(taskContainer.getTaskFragmentContainers().isEmpty()); + assertTrue(taskContainer.getSplitContainers().isEmpty()); } @Test @@ -1248,6 +1286,34 @@ public class SplitControllerTest { } @Test + public void testSplitInfoCallback_NotReportSplitIfUnstable() { + final Activity r0 = createMockActivity(); + final Activity r1 = createMockActivity(); + addSplitTaskFragments(r0, r1); + + // Should report new SplitInfo list if stable. + mSplitController.updateCallbackIfNecessary(); + assertEquals(1, mSplitInfos.size()); + + // Should not report new SplitInfo list if unstable, e.g. any Activity is finishing. + mSplitInfos.clear(); + final Activity r2 = createMockActivity(); + final Activity r3 = createMockActivity(); + doReturn(true).when(r2).isFinishing(); + addSplitTaskFragments(r2, r3); + + mSplitController.updateCallbackIfNecessary(); + assertTrue(mSplitInfos.isEmpty()); + + // Should report SplitInfo list if it becomes stable again. + mSplitInfos.clear(); + doReturn(false).when(r2).isFinishing(); + + mSplitController.updateCallbackIfNecessary(); + assertEquals(2, mSplitInfos.size()); + } + + @Test public void testSplitInfoCallback_reportSplitInMultipleTasks() { final int taskId0 = 1; final int taskId1 = 2; @@ -1363,15 +1429,13 @@ public class SplitControllerTest { TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity)); - List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID) - .mContainers; - - assertEquals(containers.get(0), tf); + final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID); + assertEquals(taskContainer.getTaskFragmentContainers().get(0), tf); mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken())); verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken())); - assertTrue(containers.isEmpty()); + assertTrue(taskContainer.getTaskFragmentContainers().isEmpty()); } @Test @@ -1381,10 +1445,8 @@ public class SplitControllerTest { bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity)); topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); - List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID) - .mContainers; - - assertEquals(containers.size(), 2); + final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID); + assertEquals(taskContainer.getTaskFragmentContainers().size(), 2); Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{ topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()}); @@ -1403,7 +1465,7 @@ public class SplitControllerTest { + "regardless of the order in ActivityStack set", topTf.getTaskFragmentToken(), fragmentTokens.get(1)); - assertTrue(containers.isEmpty()); + assertTrue(taskContainer.getTaskFragmentContainers().isEmpty()); } @Test @@ -1463,6 +1525,51 @@ public class SplitControllerTest { verify(testRecord).apply(eq(false)); } + @Test + public void testPinTopActivityStack() { + // Create two activities. + final Activity primaryActivity = createMockActivity(); + final Activity secondaryActivity = createMockActivity(); + + // Unable to pin if not being embedded. + SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(), + parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build(); + assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule)); + + // Split the two activities. + addSplitTaskFragments(primaryActivity, secondaryActivity); + final TaskFragmentContainer primaryContainer = + mSplitController.getContainerWithActivity(primaryActivity); + spyOn(primaryContainer); + + // Unable to pin if no valid TaskFragment. + doReturn(true).when(primaryContainer).isFinished(); + assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule)); + + // Otherwise, should pin successfully. + doReturn(false).when(primaryContainer).isFinished(); + assertTrue(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule)); + + // Unable to pin if there is already a pinned TaskFragment + assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule)); + + // Unable to pin on an unknown Task. + assertFalse(mSplitController.pinTopActivityStack(TASK_ID + 1, splitPinRule)); + + // Gets the current size of all the SplitContainers. + final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); + final int splitContainerCount = taskContainer.getSplitContainers().size(); + + // Create another activity and split with primary activity. + final Activity thirdActivity = createMockActivity(); + addSplitTaskFragments(primaryActivity, thirdActivity); + + // Ensure another SplitContainer is added and the pinned TaskFragment still on top + assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1); + assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity() + == secondaryActivity); + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { return createMockActivity(TASK_ID); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index 13e709271221..21889960a8b2 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -48,6 +48,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + /** * Test class for {@link TaskContainer}. * @@ -127,7 +129,7 @@ public class TaskContainerTest { assertFalse(taskContainer.isEmpty()); taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken()); - taskContainer.mContainers.clear(); + taskContainer.clearTaskFragmentContainer(); assertFalse(taskContainer.isEmpty()); } @@ -135,15 +137,15 @@ public class TaskContainerTest { @Test public void testGetTopTaskFragmentContainer() { final TaskContainer taskContainer = createTestTaskContainer(); - assertNull(taskContainer.getTopTaskFragmentContainer()); + assertNull(taskContainer.getTopNonFinishingTaskFragmentContainer()); final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */, new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */); - assertEquals(tf0, taskContainer.getTopTaskFragmentContainer()); + assertEquals(tf0, taskContainer.getTopNonFinishingTaskFragmentContainer()); final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */); - assertEquals(tf1, taskContainer.getTopTaskFragmentContainer()); + assertEquals(tf1, taskContainer.getTopNonFinishingTaskFragmentContainer()); } @Test @@ -152,17 +154,48 @@ public class TaskContainerTest { assertNull(taskContainer.getTopNonFinishingActivity()); final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class); - taskContainer.mContainers.add(tf0); + taskContainer.addTaskFragmentContainer(tf0); final Activity activity0 = mock(Activity.class); doReturn(activity0).when(tf0).getTopNonFinishingActivity(); assertEquals(activity0, taskContainer.getTopNonFinishingActivity()); final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class); - taskContainer.mContainers.add(tf1); + taskContainer.addTaskFragmentContainer(tf1); assertEquals(activity0, taskContainer.getTopNonFinishingActivity()); final Activity activity1 = mock(Activity.class); doReturn(activity1).when(tf1).getTopNonFinishingActivity(); assertEquals(activity1, taskContainer.getTopNonFinishingActivity()); } + + @Test + public void testGetSplitStatesIfStable() { + final TaskContainer taskContainer = createTestTaskContainer(); + + final SplitContainer splitContainer0 = mock(SplitContainer.class); + final SplitContainer splitContainer1 = mock(SplitContainer.class); + final SplitInfo splitInfo0 = mock(SplitInfo.class); + final SplitInfo splitInfo1 = mock(SplitInfo.class); + taskContainer.addSplitContainer(splitContainer0); + taskContainer.addSplitContainer(splitContainer1); + + // When all the SplitContainers are stable, getSplitStatesIfStable() returns the list of + // SplitInfo representing the SplitContainers. + doReturn(splitInfo0).when(splitContainer0).toSplitInfoIfStable(); + doReturn(splitInfo1).when(splitContainer1).toSplitInfoIfStable(); + + List<SplitInfo> splitInfoList = taskContainer.getSplitStatesIfStable(); + + assertEquals(2, splitInfoList.size()); + assertEquals(splitInfo0, splitInfoList.get(0)); + assertEquals(splitInfo1, splitInfoList.get(1)); + + // When any SplitContainer is in an intermediate state, getSplitStatesIfStable() returns + // null. + doReturn(null).when(splitContainer0).toSplitInfoIfStable(); + + splitInfoList = taskContainer.getSplitStatesIfStable(); + + assertNull(splitInfoList); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 78b85e642c13..cc00a49604ee 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -321,6 +321,32 @@ public class TaskFragmentContainerTest { } @Test + public void testCollectNonFinishingActivities_checkIfStable() { + final TaskContainer taskContainer = createTestTaskContainer(); + final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, + mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + + // In case mInfo is null, collectNonFinishingActivities(true) should return null. + List<Activity> activities = + container.collectNonFinishingActivities(true /* checkIfStable */); + assertNull(activities); + + // collectNonFinishingActivities(true) should return proper value when the container is in a + // stable state. + final List<IBinder> runningActivities = Lists.newArrayList(mActivity.getActivityToken()); + doReturn(runningActivities).when(mInfo).getActivities(); + container.setInfo(mTransaction, mInfo); + activities = container.collectNonFinishingActivities(true /* checkIfStable */); + assertEquals(1, activities.size()); + + // In case any activity is finishing, collectNonFinishingActivities(true) should return + // null. + doReturn(true).when(mActivity).isFinishing(); + activities = container.collectNonFinishingActivities(true /* checkIfStable */); + assertNull(activities); + } + + @Test public void testAddPendingActivity() { final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java new file mode 100644 index 000000000000..3278cdf1c337 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java @@ -0,0 +1,119 @@ +/* + * 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 androidx.window.util; + +import static org.junit.Assert.assertEquals; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.view.DisplayInfo; +import android.view.Surface; + +import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link ExtensionHelper}. + * + * Build/Install/Run: + * atest WMJetpackUnitTests:ExtensionHelperTest + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ExtensionHelperTest { + + private static final int MOCK_DISPLAY_HEIGHT = 1000; + private static final int MOCK_DISPLAY_WIDTH = 2000; + private static final int MOCK_FEATURE_LEFT = 100; + private static final int MOCK_FEATURE_RIGHT = 200; + + private static final int[] ROTATIONS = { + Surface.ROTATION_0, + Surface.ROTATION_90, + Surface.ROTATION_180, + Surface.ROTATION_270 + }; + + private static final DisplayInfo[] MOCK_DISPLAY_INFOS = { + getMockDisplayInfo(Surface.ROTATION_0), + getMockDisplayInfo(Surface.ROTATION_90), + getMockDisplayInfo(Surface.ROTATION_180), + getMockDisplayInfo(Surface.ROTATION_270), + }; + + @Test + public void testRotateRectToDisplayRotation() { + for (int rotation : ROTATIONS) { + final Rect expectedResult = getExpectedFeatureRectAfterRotation(rotation); + // The method should return correctly rotated Rect even if the requested rotation value + // differs from the rotation in DisplayInfo. This is because the WindowConfiguration is + // not always synced with DisplayInfo. + for (DisplayInfo displayInfo : MOCK_DISPLAY_INFOS) { + final Rect rect = getMockFeatureRect(); + ExtensionHelper.rotateRectToDisplayRotation(displayInfo, rotation, rect); + assertEquals( + "Result Rect should equal to expected for rotation: " + rotation + + "; displayInfo: " + displayInfo, + expectedResult, rect); + } + } + } + + @NonNull + private static DisplayInfo getMockDisplayInfo(@Surface.Rotation int rotation) { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.rotation = rotation; + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { + displayInfo.logicalWidth = MOCK_DISPLAY_WIDTH; + displayInfo.logicalHeight = MOCK_DISPLAY_HEIGHT; + } else { + displayInfo.logicalWidth = MOCK_DISPLAY_HEIGHT; + displayInfo.logicalHeight = MOCK_DISPLAY_WIDTH; + } + return displayInfo; + } + + @NonNull + private static Rect getMockFeatureRect() { + return new Rect(MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT); + } + + @NonNull + private static Rect getExpectedFeatureRectAfterRotation(@Surface.Rotation int rotation) { + switch (rotation) { + case Surface.ROTATION_0: + return new Rect( + MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT); + case Surface.ROTATION_90: + return new Rect(0, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT, + MOCK_DISPLAY_HEIGHT, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT); + case Surface.ROTATION_180: + return new Rect(MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT, 0, + MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT); + case Surface.ROTATION_270: + return new Rect(0, MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT, + MOCK_FEATURE_RIGHT); + default: + throw new IllegalArgumentException("Unknown rotation value: " + rotation); + } + } +} diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 54978bd4496d..c72a42cce2bd 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -42,16 +42,19 @@ filegroup { filegroup { name: "wm_shell_util-sources", srcs: [ - "src/com/android/wm/shell/util/**/*.java", + "src/com/android/wm/shell/animation/Interpolators.java", + "src/com/android/wm/shell/animation/PhysicsAnimator.kt", + "src/com/android/wm/shell/common/bubbles/*.kt", + "src/com/android/wm/shell/common/bubbles/*.java", + "src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt", "src/com/android/wm/shell/common/split/SplitScreenConstants.java", - "src/com/android/wm/shell/sysui/ShellSharedConstants.java", "src/com/android/wm/shell/common/TransactionPool.java", - "src/com/android/wm/shell/common/bubbles/*.java", "src/com/android/wm/shell/common/TriangleShape.java", - "src/com/android/wm/shell/animation/Interpolators.java", + "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java", "src/com/android/wm/shell/pip/PipContentOverlay.java", "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java", - "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java", + "src/com/android/wm/shell/sysui/ShellSharedConstants.java", + "src/com/android/wm/shell/util/**/*.java", ], path: "src", } @@ -112,8 +115,7 @@ genrule { name: "protolog.json.gz", srcs: [":generate-wm_shell_protolog.json"], out: ["wmshell.protolog.json.gz"], - cmd: "$(location minigzip) -c < $(in) > $(out)", - tools: ["minigzip"], + cmd: "gzip -c < $(in) > $(out)", } prebuilt_etc { @@ -148,6 +150,7 @@ android_library { ], static_libs: [ "androidx.appcompat_appcompat", + "androidx.core_core-animation", "androidx.arch.core_core-runtime", "androidx-constraintlayout_constraintlayout", "androidx.dynamicanimation_dynamicanimation", @@ -155,7 +158,7 @@ android_library { "kotlinx-coroutines-android", "kotlinx-coroutines-core", "iconloader_base", - "protolog-lib", + "com_android_wm_shell_flags_lib", "WindowManager-Shell-proto", "dagger2", "jsr330", @@ -165,7 +168,7 @@ android_library { // *.kt sources are inside a filegroup. "kotlin-annotations", ], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], manifest: "AndroidManifest.xml", plugins: ["dagger2-compiler"], } diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp new file mode 100644 index 000000000000..1a98ffcea9e7 --- /dev/null +++ b/libs/WindowManager/Shell/aconfig/Android.bp @@ -0,0 +1,12 @@ +aconfig_declarations { + name: "com_android_wm_shell_flags", + package: "com.android.wm.shell", + srcs: [ + "multitasking.aconfig", + ], +} + +java_aconfig_library { + name: "com_android_wm_shell_flags_lib", + aconfig_declarations: "com_android_wm_shell_flags", +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig new file mode 100644 index 000000000000..d55a41fc0cf7 --- /dev/null +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -0,0 +1,8 @@ +package: "com.android.wm.shell" + +flag { + name: "example_flag" + namespace: "multitasking" + description: "An Example Flag" + bug: "300136750" +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_color_selector.xml b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_color_selector.xml new file mode 100644 index 000000000000..65f5239737b2 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_color_selector.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" + android:color="@color/desktop_mode_maximize_menu_button_on_hover"/> + <item android:state_hovered="true" + android:color="@color/desktop_mode_maximize_menu_button_on_hover"/> + <item android:state_focused="true" + android:color="@color/desktop_mode_maximize_menu_button_on_hover"/> + <item android:state_selected="true" + android:color="@color/desktop_mode_maximize_menu_button_on_hover"/> + <item android:color="@color/desktop_mode_maximize_menu_button"/> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_outline_color_selector.xml b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_outline_color_selector.xml new file mode 100644 index 000000000000..86679af5428b --- /dev/null +++ b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_outline_color_selector.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" + android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/> + <item android:state_hovered="true" + android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/> + <item android:state_focused="true" + android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/> + <item android:state_selected="true" + android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/> + <item android:color="@color/desktop_mode_maximize_menu_button_outline"/> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml new file mode 100644 index 000000000000..d99d64d8da20 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml @@ -0,0 +1,24 @@ +<?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. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <ripple android:color="#99999999"> + <item android:drawable="@drawable/bubble_manage_menu_bg" /> + </ripple> + </item> + <item android:drawable="@drawable/bubble_manage_menu_bg" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml new file mode 100644 index 000000000000..02b707568cd0 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,277L180,277L180,780Q180,780 180,780Q180,780 180,780Z" /> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml index 5d7771366bec..ce242751c172 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml @@ -13,13 +13,20 @@ ~ 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"> - <group android:translateY="8.0"> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:width="128dp" + android:height="4dp" + android:viewportWidth="128" + android:viewportHeight="4" + > + <group> + <clip-path + android:pathData="M2 0H126C127.105 0 128 0.895431 128 2C128 3.10457 127.105 4 126 4H2C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0Z" + /> <path - android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/> + android:pathData="M0 0V4H128V0" + android:fillColor="@android:color/black" + /> </group> </vector> diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_handle_menu_background.xml index 4ee10f429b37..4ee10f429b37 100644 --- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_handle_menu_background.xml diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml new file mode 100644 index 000000000000..5d9fe67e8bee --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape android:shape="rectangle" + xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@android:color/white" /> + <corners android:radius="@dimen/desktop_mode_maximize_menu_corner_radius" /> +</shape> diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_maximize_button_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_maximize_button_background.xml new file mode 100644 index 000000000000..bfb0dd7f3100 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_maximize_button_background.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="@color/desktop_mode_maximize_menu_button_color_selector"/> + <corners + android:radius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"/> + <stroke android:width="1dp" android:color="@color/desktop_mode_maximize_menu_button_outline_color_selector"/> +</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_left_button_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_left_button_background.xml new file mode 100644 index 000000000000..6630fcab4794 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_left_button_background.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="@color/desktop_mode_maximize_menu_button_color_selector"/> + <corners + android:topLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius" + android:topRightRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius" + android:bottomLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius" + android:bottomRightRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius"/> + <stroke android:width="1dp" android:color="@color/desktop_mode_maximize_menu_button_outline_color_selector"/> +</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_right_button_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_right_button_background.xml new file mode 100644 index 000000000000..7bd6e9981c12 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_right_button_background.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="@color/desktop_mode_maximize_menu_button_color_selector"/> + <corners + android:topLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius" + android:topRightRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius" + android:bottomLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius" + android:bottomRightRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"/> + <stroke android:width="1dp" android:color="@color/desktop_mode_maximize_menu_button_outline_color_selector"/> +</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml new file mode 100644 index 000000000000..f4508464883d --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<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="M18.59,16.41L20,15L12,7L4,15L5.41,16.41L12,9.83" + android:fillColor="#5F6368"/> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml b/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml new file mode 100644 index 000000000000..8ef3307ee875 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<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="M4,18H20V6H4V18ZM22,18C22,19.1 21.1,20 20,20H4C2.9,20 2,19.1 2,18V6C2,4.9 2.9,4 4,4H20C21.1,4 22,4.9 22,6V18ZM13,8H18V14H13V8Z" + android:fillColor="#455A64" + android:fillType="evenOdd"/> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml new file mode 100644 index 000000000000..6e4752c9d27d --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + <path + android:fillColor="@color/compat_controls_background" + android:strokeAlpha="0.8" + android:fillAlpha="0.8" + android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/> + <group + android:translateX="12" + android:translateY="12"> + <path + android:fillColor="@color/compat_controls_text" + android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/> + </group> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml new file mode 100644 index 000000000000..141a1ce60b8e --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/compat_background_ripple"> + <item android:drawable="@drawable/user_aspect_ratio_settings_button"/> +</ripple>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml new file mode 100644 index 000000000000..a0a06f1b3721 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.wm.shell.common.bubbles.BubblePopupView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginHorizontal="@dimen/bubble_popup_margin_horizontal" + android:layout_marginTop="@dimen/bubble_popup_margin_top" + android:elevation="@dimen/bubble_manage_menu_elevation" + android:gravity="center_horizontal" + android:orientation="vertical"> + + <ImageView + android:layout_width="32dp" + android:layout_height="32dp" + android:tint="?android:attr/colorAccent" + android:contentDescription="@null" + android:src="@drawable/pip_ic_settings"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:maxWidth="@dimen/bubble_popup_content_max_width" + android:maxLines="1" + android:ellipsize="end" + android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline" + android:textColor="?android:attr/textColorPrimary" + android:text="@string/bubble_bar_education_manage_title"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:maxWidth="@dimen/bubble_popup_content_max_width" + android:textAppearance="@android:style/TextAppearance.DeviceDefault" + android:textColor="?android:attr/textColorSecondary" + android:textAlignment="center" + android:text="@string/bubble_bar_education_manage_text"/> + +</com.android.wm.shell.common.bubbles.BubblePopupView>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml new file mode 100644 index 000000000000..ddcd5c60d9c8 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="@dimen/bubble_bar_manage_menu_item_height" + android:gravity="center_vertical" + android:paddingStart="@dimen/bubble_menu_padding" + android:paddingEnd="@dimen/bubble_menu_padding" + android:background="@drawable/bubble_manage_menu_row"> + + <ImageView + android:id="@+id/bubble_bar_menu_item_icon" + android:layout_width="@dimen/bubble_bar_manage_menu_item_icon_size" + android:layout_height="@dimen/bubble_bar_manage_menu_item_icon_size" + android:contentDescription="@null"/> + + <TextView + android:id="@+id/bubble_bar_menu_item_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> + +</com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml new file mode 100644 index 000000000000..82e5aee41ff2 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.wm.shell.bubbles.bar.BubbleBarMenuView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:minWidth="@dimen/bubble_bar_manage_menu_min_width" + android:orientation="vertical" + android:elevation="@dimen/bubble_manage_menu_elevation" + android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top" + android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding" + android:paddingBottom="@dimen/bubble_bar_manage_menu_padding" + android:clipToPadding="false"> + + <LinearLayout + android:id="@+id/bubble_bar_manage_menu_bubble_section" + android:layout_width="match_parent" + android:layout_height="@dimen/bubble_bar_manage_menu_item_height" + android:orientation="horizontal" + android:gravity="center_vertical" + android:paddingStart="14dp" + android:paddingEnd="12dp" + android:background="@drawable/bubble_manage_menu_section" + android:elevation="@dimen/bubble_manage_menu_elevation"> + + <ImageView + android:id="@+id/bubble_bar_manage_menu_bubble_icon" + android:layout_width="@dimen/bubble_menu_icon_size" + android:layout_height="@dimen/bubble_menu_icon_size" + android:contentDescription="@null" /> + + <TextView + android:id="@+id/bubble_bar_manage_menu_bubble_title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_weight="1" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> + + <ImageView + android:id="@+id/bubble_bar_manage_menu_dismiss_icon" + android:layout_width="@dimen/bubble_bar_manage_menu_dismiss_icon_size" + android:layout_height="@dimen/bubble_bar_manage_menu_dismiss_icon_size" + android:layout_marginStart="8dp" + android:contentDescription="@null" + android:src="@drawable/ic_expand_less" + app:tint="?android:attr/textColorPrimary" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/bubble_bar_manage_menu_actions_section" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginTop="@dimen/bubble_bar_manage_menu_section_spacing" + android:background="@drawable/bubble_manage_menu_bg" + android:elevation="@dimen/bubble_manage_menu_elevation" /> + +</com.android.wm.shell.bubbles.bar.BubbleBarMenuView>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml new file mode 100644 index 000000000000..b489a5c1acd0 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.wm.shell.common.bubbles.BubblePopupView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|end" + android:layout_margin="@dimen/bubble_popup_margin_horizontal" + android:layout_marginBottom="120dp" + android:elevation="@dimen/bubble_manage_menu_elevation" + android:gravity="center_horizontal" + android:orientation="vertical"> + + <ImageView + android:layout_width="32dp" + android:layout_height="32dp" + android:tint="?android:attr/colorAccent" + android:contentDescription="@null" + android:src="@drawable/ic_floating_landscape"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:maxWidth="@dimen/bubble_popup_content_max_width" + android:maxLines="1" + android:ellipsize="end" + android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline" + android:textColor="?android:attr/textColorPrimary" + android:text="@string/bubble_bar_education_stack_title"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:maxWidth="@dimen/bubble_popup_content_max_width" + android:textAppearance="@android:style/TextAppearance.DeviceDefault" + android:textColor="?android:attr/textColorSecondary" + android:textAlignment="center" + android:text="@string/bubble_bar_education_stack_text"/> + +</com.android.wm.shell.common.bubbles.BubblePopupView>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml index dfaeeeb81c07..257fe1544bbb 100644 --- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml +++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml @@ -55,7 +55,7 @@ <include android:id="@+id/size_compat_hint" android:visibility="gone" - android:layout_width="@dimen/size_compat_hint_width" + android:layout_width="@dimen/compat_hint_width" android:layout_height="wrap_content" layout="@layout/compat_mode_hint"/> diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml index fb1980a52601..7e0c2071dc86 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml @@ -78,6 +78,19 @@ android:layout_weight="1"/> <ImageButton + android:id="@+id/maximize_window" + android:layout_width="40dp" + android:layout_height="40dp" + android:padding="9dp" + android:layout_marginEnd="8dp" + android:contentDescription="@string/maximize_button_text" + android:src="@drawable/decor_desktop_mode_maximize_button_dark" + android:scaleType="fitCenter" + android:gravity="end" + android:background="@null" + android:tint="@color/desktop_mode_caption_maximize_button_dark"/> + + <ImageButton android:id="@+id/close_window" android:layout_width="40dp" android:layout_height="40dp" diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml index 1d6864c152c2..d93e9ba32105 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml @@ -25,9 +25,9 @@ <ImageButton android:id="@+id/caption_handle" - android:layout_width="176dp" + android:layout_width="128dp" android:layout_height="42dp" - android:paddingHorizontal="24dp" + android:paddingVertical="19dp" android:contentDescription="@string/handle_text" android:src="@drawable/decor_handle_dark" tools:tint="@color/desktop_mode_caption_handle_bar_dark" diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml index 167a003932d6..c03d240d59f2 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml @@ -19,7 +19,7 @@ android:layout_width="@dimen/desktop_mode_handle_menu_width" android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height" android:orientation="horizontal" - android:background="@drawable/desktop_mode_decor_menu_background" + android:background="@drawable/desktop_mode_decor_handle_menu_background" android:gravity="center_vertical"> <ImageView diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml index 40a4b53f3e1d..cdf4937599c9 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml @@ -18,7 +18,7 @@ android:layout_width="@dimen/desktop_mode_handle_menu_width" android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height" android:orientation="vertical" - android:background="@drawable/desktop_mode_decor_menu_background"> + android:background="@drawable/desktop_mode_decor_handle_menu_background"> <Button android:id="@+id/screenshot_button" diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml index 95283b9e214a..08d91498b338 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml @@ -18,7 +18,7 @@ android:layout_width="@dimen/desktop_mode_handle_menu_width" android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height" android:orientation="horizontal" - android:background="@drawable/desktop_mode_decor_menu_background" + android:background="@drawable/desktop_mode_decor_handle_menu_background" android:gravity="center_vertical"> <ImageButton diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml new file mode 100644 index 000000000000..0db72f7be8e6 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + style="?android:attr/buttonBarStyle" + android:layout_width="@dimen/desktop_mode_maximize_menu_width" + android:layout_height="@dimen/desktop_mode_maximize_menu_height" + android:orientation="horizontal" + android:gravity="center" + android:background="@drawable/desktop_mode_maximize_menu_background"> + + + <Button + android:id="@+id/maximize_menu_maximize_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="120dp" + android:layout_height="80dp" + android:layout_marginRight="15dp" + android:color="@color/desktop_mode_maximize_menu_button" + android:background="@drawable/desktop_mode_maximize_menu_maximize_button_background" + android:stateListAnimator="@null"/> + + <Button + android:id="@+id/maximize_menu_snap_left_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="58dp" + android:layout_height="80dp" + android:layout_marginRight="6dp" + android:color="@color/desktop_mode_maximize_menu_button" + android:background="@drawable/desktop_mode_maximize_menu_snap_left_button_background" + android:stateListAnimator="@null"/> + + <Button + android:id="@+id/maximize_menu_snap_right_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="58dp" + android:layout_height="80dp" + android:color="@color/desktop_mode_maximize_menu_button" + android:background="@drawable/desktop_mode_maximize_menu_snap_right_button_background" + android:stateListAnimator="@null"/> +</LinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml new file mode 100644 index 000000000000..433d8546ece0 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.wm.shell.compatui.UserAspectRatioSettingsLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="bottom|end"> + + <include android:id="@+id/user_aspect_ratio_settings_hint" + android:visibility="gone" + android:layout_width="@dimen/compat_hint_width" + android:layout_height="wrap_content" + layout="@layout/compat_mode_hint"/> + + <ImageButton + android:id="@+id/user_aspect_ratio_settings_button" + android:visibility="gone" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/compat_button_margin" + android:layout_marginBottom="@dimen/compat_button_margin" + android:src="@drawable/user_aspect_ratio_settings_button_ripple" + android:background="@android:color/transparent" + android:contentDescription="@string/user_aspect_ratio_settings_button_description"/> + +</com.android.wm.shell.compatui.UserAspectRatioSettingsLayout> diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index ee00e2651f62..6622973007b4 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Verander grootte"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Hou vas"</string> <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_forced_resizable" msgid="7429086980048964687">"App sal dalk nie met verdeelde skerm werk nie"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App 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_divider" msgid="6407584574218956849">"Skermverdeler"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bo 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Bo 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Volskerm onder"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Verdeel links"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Verdeel regs"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Verdeel bo"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Verdeel onder"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Gebruik eenhandmodus"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Swiep van die onderkant van die skerm af op of tik enige plek bo die program om uit te gaan"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Begin eenhandmodus"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Het dit"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Geen onlangse borrels nie"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Onlangse borrels en borrels wat toegemaak is, sal hier verskyn"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Beheer borrels enige tyd"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tik hier om te bestuur watter apps en gesprekke in borrels kan verskyn"</string> <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="6712141648865547958">"Tik om hierdie program te herbegin vir ’n beter aansig."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tik om hierdie app te herbegin vir ’n beter aansig"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Verander hierdie app se aspekverhouding in Instellings"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Verander aspekverhouding"</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="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_split_screen_text" msgid="449233070804658627">"Sleep ’n ander app 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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselleer"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Herbegin"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Moenie weer wys nie"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dubbeltik om\nhierdie app te skuif"</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="app_icon_text" msgid="2823268023931811747">"Appikoon"</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> + <string name="select_text" msgid="5139083974039906583">"Kies"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Skermskoot"</string> + <string name="close_text" msgid="4986518933445178928">"Maak toe"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Maak kieslys oop"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index 781038f1e61e..a3f77411b0f6 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -32,31 +32,27 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"መጠን ይቀይሩ"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string> <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_forced_resizable" msgid="7429086980048964687">"መተግበሪያ ከተከፈለ ማያ ገፅ ጋር ላይሠራ ይችላል"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም"</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_divider" msgid="6407584574218956849">"የተከፈለ የማያ ገፅ ከፋይ"</string> + <string name="divider_title" msgid="1963391955593749442">"የተከፈለ የማያ ገፅ ከፋይ"</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> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ግራ 30%"</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_right_full" msgid="3408505054325944903">"የቀኝ ሙሉ ማያ ገፅ"</string> + <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"የላይ ሙሉ ማያ ገፅ"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ከላይ 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ከላይ 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ከላይ 30%"</string> - <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"የታች ሙሉ ማያ ገጽ"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"የታች ሙሉ ማያ ገፅ"</string> + <string name="accessibility_split_left" msgid="1713683765575562458">"ወደ ግራ ከፋፍል"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"ወደ ቀኝ ከፋፍል"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"ወደ ላይ ከፋፍል"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"ወደ ታች ከፋፍል"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ባለአንድ እጅ ሁነታን በመጠቀም ላይ"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ለመውጣት ከማያው ግርጌ ወደ ላይ ይጥረጉ ወይም ከመተግበሪያው በላይ ማንኛውም ቦታ ላይ መታ ያድርጉ"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ባለአንድ እጅ ሁነታ ጀምር"</string> @@ -80,16 +76,24 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"በማንኛውም ጊዜ ዓረፋዎችን ይቆጣጠሩ"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"የትኛዎቹ መተግበሪያዎች እና ውይይቶች ዓረፋ መፍጠር እንደሚችሉ ለማስተዳደር እዚህ ጋር መታ ያድርጉ"</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="6712141648865547958">"ለተሻለ ዕይታ ይህን መተግበሪያ ዳግም ለማስነሳት መታ ያድርጉ።"</string> + <string name="restart_button_description" msgid="4564728020654658478">"ለተሻለ ዕይታ ይህን መተግበሪያ እንደገና ለመጀመር መታ ያድርጉ"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"የዚህን መተግበሪያ ምጥጥነ ገፅታ በቅንብሮች ውስጥ ይለውጡ"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"የምጥጥነ ገፅታ ለውጥ"</string> <string name="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="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_split_screen_text" msgid="449233070804658627">"ለተከፈለ ማያ ገፅ ሌላ መተግበሪያ ይጎትቱ"</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="letterbox_restart_dialog_title" msgid="8543049527871033505">"ለተሻለ ዕይታ እንደገና ይጀመር?"</string> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ይቅር"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"እንደገና ያስጀምሩ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ዳግም አታሳይ"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ይህን መተግበሪያ\nለማንቀሳቀስ ሁለቴ መታ ያድርጉ"</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="app_icon_text" msgid="2823268023931811747">"የመተግበሪያ አዶ"</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="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገፅ"</string> <string name="more_button_text" msgid="3655388105592893530">"ተጨማሪ"</string> <string name="float_button_text" msgid="9221657008391364581">"ተንሳፋፊ"</string> + <string name="select_text" msgid="5139083974039906583">"ምረጥ"</string> + <string name="screenshot_text" msgid="1477704010087786671">"ቅጽበታዊ ገፅ ዕይታ"</string> + <string name="close_text" msgid="4986518933445178928">"ዝጋ"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"ምናሌን ክፈት"</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 a6be57889a4e..84c1c6763d43 100644 --- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml @@ -20,7 +20,7 @@ <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ሥዕል-ላይ-ሥዕል"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ርዕስ የሌለው ፕሮግራም)"</string> <string name="pip_close" msgid="2955969519031223530">"ዝጋ"</string> - <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</string> + <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገፅ"</string> <string name="pip_move" msgid="158770205886688553">"ውሰድ"</string> <string name="pip_expand" msgid="1051966011679297308">"ዘርጋ"</string> <string name="pip_collapse" msgid="3903295106641385962">"ሰብስብ"</string> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index 7325da135d94..ee4302e461df 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"قد لا يعمل التطبيق بشكل سليم في وضع تقسيم الشاشة."</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"لا يعمل التطبيق في وضع تقسيم الشاشة."</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_divider" msgid="6407584574218956849">"أداة تقسيم الشاشة"</string> + <string name="divider_title" msgid="1963391955593749442">"أداة تقسيم الشاشة"</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> @@ -49,14 +49,10 @@ <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> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"تقسيم لليسار"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"تقسيم لليمين"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"تقسيم للأعلى"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"تقسيم للأسفل"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"استخدام وضع \"التصفح بيد واحدة\""</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"للخروج، مرِّر سريعًا من أسفل الشاشة إلى أعلاها أو انقر في أي مكان فوق التطبيق."</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"بدء وضع \"التصفح بيد واحدة\""</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"التحكّم في إظهار الفقاعات في أي وقت"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"انقر هنا للتحكّم في إظهار فقاعات التطبيقات والمحادثات التي تريدها."</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="6712141648865547958">"انقر لإعادة تشغيل هذا التطبيق للحصول على عرض أفضل."</string> + <string name="restart_button_description" msgid="4564728020654658478">"انقر لإعادة تشغيل هذا التطبيق للحصول على تجربة عرض أفضل."</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"يمكنك تغيير نسبة العرض إلى الارتفاع لهذا التطبيق من خلال \"الإعدادات\"."</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تغيير نسبة العرض إلى الارتفاع"</string> <string name="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="7739895354143295358">"استخدام تطبيقات متعدّدة في وقت واحد"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"إلغاء"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"إعادة التشغيل"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"عدم عرض مربّع حوار التأكيد مجددًا"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"انقر مرّتَين لنقل\nهذا التطبيق."</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="app_icon_text" msgid="2823268023931811747">"رمز التطبيق"</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> + <string name="select_text" msgid="5139083974039906583">"اختيار"</string> + <string name="screenshot_text" msgid="1477704010087786671">"لقطة شاشة"</string> + <string name="close_text" msgid="4986518933445178928">"إغلاق"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"فتح القائمة"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index 3fd705e8a07b..a568d5841d32 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"এপ্টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"এপ্টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে"</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_divider" msgid="6407584574218956849">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string> + <string name="divider_title" msgid="1963391955593749442">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</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> @@ -49,14 +49,10 @@ <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> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"বাওঁফালে বিভাজন কৰক"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"সোঁফালে বিভাজন কৰক"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"একেবাৰে ওপৰৰফালে বিভাজন কৰক"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"একেবাৰে তলৰফালে বিভাজন কৰক"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"এখন হাতেৰে ব্যৱহাৰ কৰা ম\'ডটো আৰম্ভ কৰক"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"বুজি পালোঁ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"কোনো শেহতীয়া bubbles নাই"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"শেহতীয়া bubbles আৰু অগ্ৰাহ্য কৰা bubbles ইয়াত প্ৰদর্শিত হ\'ব"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"যিকোনো সময়তে বাবল নিয়ন্ত্ৰণ কৰক"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"কোনবোৰ এপ্ আৰু বাৰ্তালাপ বাবল হ’ব পাৰে সেয়া পৰিচালনা কৰিবলৈ ইয়াত টিপক"</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="6712141648865547958">"উন্নত ভিউৰ বাবে এপ্টো ৰিষ্টাৰ্ট কৰিবলৈ টিপক।"</string> + <string name="restart_button_description" msgid="4564728020654658478">"উন্নত ভিউ পোৱাৰ বাবে এপ্টো ৰিষ্টাৰ্ট কৰিবলৈ টিপক"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ছেটিঙলৈ গৈ এই এপ্টোৰ আকাৰৰ অনুপাত সলনি কৰক"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"আকাৰৰ অনুপাত সলনি কৰক"</string> <string name="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="7739895354143295358">"চাওক আৰু অধিক কৰক"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্ টানি আনি এৰক"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্ টানি আনি এৰক"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"বাতিল কৰক"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"ৰিষ্টাৰ্ট কৰক"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"পুনৰাই নেদেখুৱাব"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"এই এপ্টো\nস্থানান্তৰ কৰিবলৈ দুবাৰ টিপক"</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="app_icon_text" msgid="2823268023931811747">"এপৰ চিহ্ন"</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> + <string name="select_text" msgid="5139083974039906583">"বাছনি কৰক"</string> + <string name="screenshot_text" msgid="1477704010087786671">"স্ক্ৰীনশ্বট"</string> + <string name="close_text" msgid="4986518933445178928">"বন্ধ কৰক"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"মেনু খোলক"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index a31b1e7eb9cd..1a681e1c63a6 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ölçüsünü dəyişin"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Güvənli məkanda saxlayın"</string> <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_forced_resizable" msgid="7429086980048964687">"Tətbiq bölünmüş ekranda işləməyə bilər"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Tətbiq bölünmüş ekranı 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_divider" msgid="6407584574218956849">"Bölünmüş ekran ayırıcısı"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yuxarı 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Yuxarı 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Aşağı tam ekran"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Sola ayırın"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Sağa ayırın"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Yuxarı ayırın"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Aşağı ayırın"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Birəlli rejim istifadəsi"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Çıxmaq üçün ekranın aşağısından yuxarıya doğru sürüşdürün və ya tətbiqin yuxarısında istənilən yerə toxunun"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Birəlli rejim başlasın"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Anladım"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Yumrucuqlar yoxdur"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Son yumrucuqlar və buraxılmış yumrucuqlar burada görünəcək"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Yumrucuqları idarə edin"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Bura toxunaraq yumrucuq göstərəcək tətbiq və söhbətləri idarə edin"</string> <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="6712141648865547958">"Toxunaraq bu tətbiqi yenidən başladın ki, daha görüntü əldə edəsiniz."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Yaxşı görünüş üçün toxunaraq bu tətbiqi yenidən başladın"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ayarlarda bu tətbiqin tərəflər nisbətini dəyişin"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tərəflər nisbətini dəyişin"</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="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_split_screen_text" msgid="449233070804658627">"Bölünmüş ekran üçün başqa tətbiq sürüşdürün"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ləğv edin"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yenidən başladın"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Yenidən göstərməyin"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tətbiqi köçürmək üçün\niki dəfə toxunun"</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="app_icon_text" msgid="2823268023931811747">"Tətbiq ikonası"</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> + <string name="select_text" msgid="5139083974039906583">"Seçin"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Skrinşot"</string> + <string name="close_text" msgid="4986518933445178928">"Bağlayın"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Menyunu açın"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml index c0e4789d3d97..cba293b20c3d 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Promenite veličinu"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stavite u tajnu memoriju"</string> <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_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće raditi sa podeljenim ekranom."</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Razdelnik podeljenog ekrana"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji ekran 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gornji ekran 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Režim celog ekrana za donji ekran"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Podelite levo"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Podelite desno"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Podelite u vrhu"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podelite u dnu"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korišćenje režima jednom rukom"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Da biste izašli, prevucite nagore od dna ekrana ili dodirnite bilo gde iznad aplikacije"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pokrenite režim jednom rukom"</string> @@ -80,31 +76,46 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Važi"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nema nedavnih oblačića"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Ovde se prikazuju nedavni i odbačeni oblačići"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrolišite oblačiće u svakom trenutku"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovde i odredite koje aplikacije i konverzacije mogu da imaju oblačić"</string> <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="6712141648865547958">"Dodirnite da biste restartovali ovu aplikaciju radi boljeg prikaza."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da biste restartovali ovu aplikaciju radi boljeg prikaza"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promenite razmeru ove aplikacije u Podešavanjima"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promeni razmeru"</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="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_split_screen_text" msgid="449233070804658627">"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="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite li da restartujete radi boljeg prikaza?"</string> - <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, s tim što možete da izgubite ono što ste uradili ili nesačuvane promene, ako ih ima"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, ali možete da izgubite napredak ili nesačuvane promene"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartuj"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvaput dodirnite da biste\npremestili ovu aplikaciju"</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="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</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> + <string name="select_text" msgid="5139083974039906583">"Izaberite"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Snimak ekrana"</string> + <string name="close_text" msgid="4986518933445178928">"Zatvorite"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Otvorite meni"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index b67e2cdca49e..80e5a677e86d 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Праграма можа не працаваць у рэжыме падзеленага экрана"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Праграма не падтрымлівае рэжым падзеленага экрана"</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_divider" msgid="6407584574218956849">"Раздзяляльнік падзеленага экрана"</string> + <string name="divider_title" msgid="1963391955593749442">"Раздзяляльнік падзеленага экрана"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхні экран – 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Верхні экран – 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ніжні экран – поўнаэкранны рэжым"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Падзяліць злева"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Падзяліць справа"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Падзяліць уверсе"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Падзяліць унізе"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Выкарыстоўваецца рэжым кіравання адной рукой"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Каб выйсці, правядзіце па экране пальцам знізу ўверх або націсніце ў любым месцы над праграмай"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Запусціць рэжым кіравання адной рукой"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Кіруйце наладамі ўсплывальных апавяшчэнняў у любы час"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Каб кіраваць усплывальнымі апавяшчэннямі для праграм і размоў, націсніце тут"</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="6712141648865547958">"Націсніце, каб перазапусціць гэту праграму для лепшага прагляду."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Націсніце, каб перазапусціць гэту праграму для зручнейшага прагляду"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Змяніць суадносіны бакоў для гэтай праграмы ў наладах"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Змяніць суадносіны бакоў"</string> <string name="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="7739895354143295358">"Адначасова выконвайце розныя задачы"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Скасаваць"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перазапусціць"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больш не паказваць"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Каб перамясціць праграму,\nнацісніце двойчы"</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="app_icon_text" msgid="2823268023931811747">"Значок праграмы"</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> + <string name="select_text" msgid="5139083974039906583">"Выбраць"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Здымак экрана"</string> + <string name="close_text" msgid="4986518933445178928">"Закрыць"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Адкрыць меню"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index 9bf396dc04c9..ca5923919d36 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Приложението може да не работи в режим на разделен екран"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Приложението не поддържа разделен екран"</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_divider" msgid="6407584574218956849">"Разделител в режима за разделен екран"</string> + <string name="divider_title" msgid="1963391955593749442">"Разделител в режима за разделен екран"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горен екран: 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Горен екран: 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Долен екран: Показване на цял екран"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Разделяне в лявата част"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Разделяне в дясната част"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Разделяне в горната част"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Разделяне в долната част"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Използване на режима за работа с една ръка"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"За изход прекарайте пръст нагоре от долната част на екрана или докоснете произволно място над приложението"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Стартиране на режима за работа с една ръка"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Управление на балончетата по всяко време"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Докоснете тук, за да управл. кои прил. и разговори могат да показват балончета"</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="6712141648865547958">"Докоснете, за да рестартирате това приложение с цел по-добър изглед."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Докоснете, за да рестартирате това приложение с цел по-добър изглед"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Променете съотношението на това приложение в „Настройки“"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Промяна на съотношението"</string> <string name="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="7739895354143295358">"Преглеждайте и правете повече неща"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Отказ"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартиране"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Да не се показва отново"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Докоснете двукратно, за да\nпреместите това приложение"</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="app_icon_text" msgid="2823268023931811747">"Икона на приложението"</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> + <string name="select_text" msgid="5139083974039906583">"Избиране"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Екранна снимка"</string> + <string name="close_text" msgid="4986518933445178928">"Затваряне"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Отваряне на менюто"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index affb62e74591..c1eb469f73a4 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"স্প্লিট স্ক্রিনে এই অ্যাপ নাও কাজ করতে পারে"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"স্প্লিট স্ক্রিনে এই অ্যাপ কাজ করে না"</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_divider" msgid="6407584574218956849">"স্প্লিট স্ক্রিন বিভাজক"</string> + <string name="divider_title" msgid="1963391955593749442">"স্প্লিট স্ক্রিন বিভাজক"</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> @@ -49,14 +49,10 @@ <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> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"স্ক্রিনের বাঁদিকে স্প্লিট করুন"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"স্ক্রিনের ডানদিকে স্প্লিট করুন"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"স্ক্রিনের উপরের দিকে স্প্লিট করুন"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"স্প্লিট করার বোতাম"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"\'এক হাতে ব্যবহার করার মোড\'-এর ব্যবহার"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"বেরিয়ে আসার জন্য, স্ক্রিনের নিচ থেকে উপরের দিকে সোয়াইপ করুন অথবা অ্যাপ আইকনের উপরে যেকোনও জায়গায় ট্যাপ করুন"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"\'এক হাতে ব্যবহার করার মোড\' শুরু করুন"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"যেকোনও সময় বাবল নিয়ন্ত্রণ করুন"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"কোন অ্যাপ ও কথোপকথনের জন্য বাবলের সুবিধা চান তা ম্যানেজ করতে এখানে ট্যাপ করুন"</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="6712141648865547958">"আরও ভাল ভিউয়ের জন্য এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন।"</string> + <string name="restart_button_description" msgid="4564728020654658478">"আরও ভাল ভিউয়ের জন্য এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"সেটিংস থেকে এই অ্যাপের অ্যাস্পেক্ট রেশিও পরিবর্তন করুন"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"অ্যাস্পেক্ট রেশিও পরিবর্তন করুন"</string> <string name="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="7739895354143295358">"দেখুন ও আরও অনেক কিছু করুন"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"স্প্লিট স্ক্রিনের জন্য অন্য অ্যাপে টেনে আনুন"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"স্প্লিট স্ক্রিনের ক্ষেত্রে অন্য কোনও অ্যাপ টেনে আনুন"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"বাতিল করুন"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"রিস্টার্ট করুন"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"আর দেখতে চাই না"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"এই অ্যাপ সরাতে\nডবল ট্যাপ করুন"</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="app_icon_text" msgid="2823268023931811747">"অ্যাপ আইকন"</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> + <string name="select_text" msgid="5139083974039906583">"বেছে নিন"</string> + <string name="screenshot_text" msgid="1477704010087786671">"স্ক্রিনশট"</string> + <string name="close_text" msgid="4986518933445178928">"বন্ধ করুন"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"মেনু খুলুন"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index dd2f871c5ebb..c97fc3dffb6d 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Promjena veličine"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stavljanje u stash"</string> <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_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće funkcionirati na podijeljenom ekranu"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podržava podijeljeni ekran"</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 podijeljenog ekrana"</string> - <string name="divider_title" msgid="5482989479865361192">"Razdjelnik podijeljenog ekrana"</string> + <string name="accessibility_divider" msgid="6407584574218956849">"Razdjelnik podijeljenog ekrana"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gore 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gore 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Donji ekran kao cijeli ekran"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Podjela ulijevo"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Podjela udesno"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Podjela nagore"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podjela nadolje"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korištenje načina rada jednom rukom"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Da izađete, prevucite s dna ekrana prema gore ili dodirnite bilo gdje iznad aplikacije"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Započinjanje načina rada jednom rukom"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Razumijem"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nema nedavnih oblačića"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nedavni i odbačeni oblačići će se pojaviti ovdje"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljajte oblačićima u svakom trenutku"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovdje da upravljate time koje aplikacije i razgovori mogu imati oblačić"</string> <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="6712141648865547958">"Dodirnite da ponovo pokrenete ovu aplikaciju radi boljeg prikaza."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da ponovo pokrenete ovu aplikaciju radi boljeg prikaza"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promijenite format slike aplikacije u Postavkama"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promijenite format slike"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Ponovo pokreni"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dodirnite dvaput da\npomjerite aplikaciju"</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="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</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> + <string name="select_text" msgid="5139083974039906583">"Odabir"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Snimak ekrana"</string> + <string name="close_text" msgid="4986518933445178928">"Zatvaranje"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Otvaranje menija"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index 937e0d9d4b3f..a9195e4dbc03 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Canvia la mida"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Amaga"</string> <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_forced_resizable" msgid="7429086980048964687">"És possible que l\'aplicació no funcioni amb la pantalla dividida"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Separador de pantalla dividida"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,20 +49,16 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Pantalla superior al 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Pantalla superior al 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Divideix a l\'esquerra"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Divideix a la dreta"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Divideix a la part superior"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Divideix a la part inferior"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"S\'està utilitzant el mode d\'una mà"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Per sortir, llisca cap amunt des de la part inferior de la pantalla o toca qualsevol lloc a sobre de l\'aplicació"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Inicia el mode d\'una mà"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Surt del mode d\'una mà"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Configuració de les bombolles: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> - <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú addicional"</string> + <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú de desbordament"</string> <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Torna a afegir a la pila"</string> <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de: <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> (<xliff:g id="APP_NAME">%2$s</xliff:g>) i <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> més"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entesos"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No hi ha bombolles recents"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bombolles recents i les ignorades es mostraran aquí"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controla les bombolles en qualsevol moment"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toca aquí per gestionar quines aplicacions i converses poden fer servir bombolles"</string> <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="6712141648865547958">"Toca per reiniciar aquesta aplicació i obtenir una millor visualització."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Toca per reiniciar aquesta aplicació i obtenir una millor visualització"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Canvia la relació d\'aspecte d\'aquesta aplicació a Configuració"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Canvia la relació d\'aspecte"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel·la"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reinicia"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No ho tornis a mostrar"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Fes doble toc per\nmoure aquesta aplicació"</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="app_icon_text" msgid="2823268023931811747">"Icona de l\'aplicació"</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> + <string name="select_text" msgid="5139083974039906583">"Selecciona"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string> + <string name="close_text" msgid="4986518933445178928">"Tanca"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Obre el menú"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index a35889546ec9..89bd22203c8c 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Změnit velikost"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Uložit"</string> <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_forced_resizable" msgid="7429086980048964687">"Aplikace v režimu rozdělené obrazovky nemusí fungovat"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Čára rozdělující obrazovku"</string> + <string name="divider_title" msgid="1963391955593749442">"Čá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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % nahoře"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % nahoře"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolní část na celou obrazovku"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Rozdělit vlevo"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Rozdělit vpravo"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Rozdělit nahoře"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Rozdělit dole"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Používání režimu jedné ruky"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Režim ukončíte, když přejedete prstem z dolní části obrazovky nahoru nebo klepnete kamkoli nad aplikaci"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Spustit režim jedné ruky"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Žádné nedávné bubliny"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Zde se budou zobrazovat nedávné bubliny a zavřené bubliny"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Nastavení bublin můžete kdykoli upravit"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Klepnutím sem lze spravovat, které aplikace a konverzace mohou vytvářet bubliny"</string> <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="6712141648865547958">"Klepnutím tuto aplikaci kvůli lepšímu zobrazení restartujete."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Klepnutím tuto aplikaci kvůli lepšímu zobrazení restartujete"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Změnit v Nastavení poměr stran této aplikace"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Změnit poměr stran"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušit"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartovat"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Tuto zprávu příště nezobrazovat"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvojitým klepnutím\npřesunete aplikaci"</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="app_icon_text" msgid="2823268023931811747">"Ikona aplikace"</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> + <string name="select_text" msgid="5139083974039906583">"Vybrat"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Snímek obrazovky"</string> + <string name="close_text" msgid="4986518933445178928">"Zavřít"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Otevřít nabídku"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index 7a40efd0382c..fd880bcfbd0c 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Rediger størrelse"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Skjul"</string> <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_forced_resizable" msgid="7429086980048964687">"Appen fungerer muligvis ikke i opdelt skærm"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Adskiller til opdelt skærm"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Øverste 50 %"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Øverste 30 %"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Vis nederste del i fuld skærm"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Vis i venstre side"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Vis i højre side"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Vis øverst"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Vis nederst"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Brug af enhåndstilstand"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Du kan afslutte ved at stryge opad fra bunden af skærmen eller trykke et vilkårligt sted over appen"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start enhåndstilstand"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen seneste bobler"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nye bobler og afviste bobler vises her"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Administrer bobler når som helst"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tryk her for at administrere, hvilke apps og samtaler der kan vises i bobler"</string> <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="6712141648865547958">"Tryk for at genstarte denne app, så visningen forbedres."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tryk for at genstarte denne app, så visningen forbedres"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Skift denne apps billedformat i Indstillinger"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Skift billedformat"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuller"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Genstart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vis ikke igen"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tryk to gange\nfor at flytte appen"</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="app_icon_text" msgid="2823268023931811747">"Appikon"</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> + <string name="select_text" msgid="5139083974039906583">"Vælg"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Luk"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Åbn menu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index 8f6275209280..b28394d27e9a 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -20,7 +20,7 @@ <string name="pip_phone_close" msgid="5783752637260411309">"Schließen"</string> <string name="pip_phone_expand" msgid="2579292903468287504">"Maximieren"</string> <string name="pip_phone_settings" msgid="5468987116750491918">"Einstellungen"</string> - <string name="pip_phone_enter_split" msgid="7042877263880641911">"„Geteilter Bildschirm“ aktivieren"</string> + <string name="pip_phone_enter_split" msgid="7042877263880641911">"Splitscreen 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> @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Größe anpassen"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"In Stash legen"</string> <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_forced_resizable" msgid="7429086980048964687">"Die App funktioniert im Splitscreen-Modus unter Umständen nicht"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Splitscreen 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_divider" msgid="6407584574218956849">"Bildschirmteiler"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % oben"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % oben"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Vollbild unten"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Links teilen"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Rechts teilen"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Oben teilen"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Unten teilen"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Einhandmodus wird verwendet"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Wenn du die App schließen möchtest, wische vom unteren Rand des Displays nach oben oder tippe auf eine beliebige Stelle oberhalb der App"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Einhandmodus starten"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Keine kürzlich geschlossenen Bubbles"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Hier werden aktuelle und geschlossene Bubbles angezeigt"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubble-Einstellungen festlegen"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tippe hier, um zu verwalten, welche Apps und Unterhaltungen als Bubble angezeigt werden können"</string> <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="6712141648865547958">"Tippe, um diese App neu zu starten und die Ansicht zu verbessern."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tippen, um diese App neu zu starten und die Ansicht zu verbessern"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Seitenverhältnis der App in den Einstellungen ändern"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Seitenverhältnis ändern"</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="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_split_screen_text" msgid="449233070804658627">"Für Splitscreen-Modus weitere App hineinziehen"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Abbrechen"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Neu starten"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nicht mehr anzeigen"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Zum Verschieben\ndoppeltippen"</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="app_icon_text" msgid="2823268023931811747">"App-Symbol"</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="split_screen_text" msgid="1396336058129570886">"Splitscreen"</string> <string name="more_button_text" msgid="3655388105592893530">"Mehr"</string> <string name="float_button_text" msgid="9221657008391364581">"Frei schwebend"</string> + <string name="select_text" msgid="5139083974039906583">"Auswählen"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Schließen"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Menü öffnen"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index 8d0faebf269d..684c3bbddd82 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</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_divider" msgid="6407584574218956849">"Διαχωριστικό οθόνης"</string> + <string name="divider_title" msgid="1963391955593749442">"Διαχωριστικό οθόνης"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Πάνω 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Πάνω 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Κάτω πλήρης οθόνη"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Διαχωρισμός αριστερά"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Διαχωρισμός δεξιά"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Διαχωρισμός επάνω"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Διαχωρισμός κάτω"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Χρήση λειτουργίας ενός χεριού"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Για έξοδο, σύρετε προς τα πάνω από το κάτω μέρος της οθόνης ή πατήστε οπουδήποτε πάνω από την εφαρμογή."</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Έναρξη λειτουργίας ενός χεριού"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Ελέγξτε τα συννεφάκια ανά πάσα στιγμή."</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Πατήστε εδώ για τη διαχείριση εφαρμογών και συζητήσεων που προβάλλουν συννεφάκια"</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="6712141648865547958">"Πατήστε για να επανεκκινήσετε αυτή την εφαρμογή για καλύτερη προβολή."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Πατήστε για να επανεκκινήσετε αυτή την εφαρμογή για καλύτερη προβολή"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Αλλάξτε τον λόγο διαστάσεων αυτής της εφαρμογής στις Ρυθμίσεις"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Αλλαγή λόγου διαστάσεων"</string> <string name="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="7739895354143295358">"Δείτε και κάντε περισσότερα"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης."</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ακύρωση"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Επανεκκίνηση"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Να μην εμφανιστεί ξανά"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Πατήστε δύο φορές για\nμετακίνηση αυτής της εφαρμογής"</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="app_icon_text" msgid="2823268023931811747">"Εικονίδιο εφαρμογής"</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> + <string name="select_text" msgid="5139083974039906583">"Επιλογή"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Στιγμιότυπο οθόνης"</string> + <string name="close_text" msgid="4986518933445178928">"Κλείσιμο"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Άνοιγμα μενού"</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 a108e8930251..1890c3d28591 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string> <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_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Split screen divider"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -76,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles at any time"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</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="6712141648865547958">"Tap to restart this app for a better view."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -93,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</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="app_icon_text" msgid="2823268023931811747">"App icon"</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> + <string name="select_text" msgid="5139083974039906583">"Select"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Close"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</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 cfa9abc5bc13..72189df6d65d 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string> <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_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Split screen divider"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -76,15 +76,21 @@ <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="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string> + <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles anytime"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</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="6712141648865547958">"Tap to restart this app for a better view."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -93,14 +99,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don’t show again"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</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="app_icon_text" msgid="2823268023931811747">"App Icon"</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> + <string name="select_text" msgid="5139083974039906583">"Select"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Close"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Open Menu"</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 a108e8930251..1890c3d28591 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string> <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_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Split screen divider"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -76,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles at any time"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</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="6712141648865547958">"Tap to restart this app for a better view."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -93,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</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="app_icon_text" msgid="2823268023931811747">"App icon"</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> + <string name="select_text" msgid="5139083974039906583">"Select"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Close"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</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 a108e8930251..1890c3d28591 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string> <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_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Split screen divider"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -76,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles at any time"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</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="6712141648865547958">"Tap to restart this app for a better view."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -93,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</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="app_icon_text" msgid="2823268023931811747">"App icon"</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> + <string name="select_text" msgid="5139083974039906583">"Select"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Close"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</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 65af7781ad98..294bdb5edf3b 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string> <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_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Split screen divider"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -76,15 +76,21 @@ <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="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string> + <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles anytime"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</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="6712141648865547958">"Tap to restart this app for a better view."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -93,14 +99,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don’t show again"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</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="app_icon_text" msgid="2823268023931811747">"App Icon"</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> + <string name="select_text" msgid="5139083974039906583">"Select"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Close"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Open Menu"</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 c26532eb3860..54f2de06e5fb 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Cambiar el tamaño"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Almacenar de manera segura"</string> <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_forced_resizable" msgid="7429086980048964687">"Es posible que la app no funcione en el modo de pantalla dividida"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +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> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir a la izquierda"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir a la derecha"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir en la parte superior"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir en la parte inferior"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Cómo usar el modo de 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 presiona cualquier parte arriba de la app"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar el modo de una mano"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No hay burbujas recientes"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Las burbujas recientes y las que se descartaron aparecerán aquí"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controla las burbujas"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Presiona para administrar las apps y conversaciones que pueden mostrar burbujas"</string> <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="6712141648865547958">"Presiona para reiniciar esta app y tener una mejor vista."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Presiona para reiniciar esta app y tener una mejor vista"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambiar la relación de aspecto de esta app en Configuración"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar relación de aspecto"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Presiona dos veces\npara mover esta app"</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="app_icon_text" msgid="2823268023931811747">"Ícono de la app"</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> + <string name="select_text" msgid="5139083974039906583">"Seleccionar"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string> + <string name="close_text" msgid="4986518933445178928">"Cerrar"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Abrir el menú"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index 58deed555fd9..19a8a1402022 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Cambiar tamaño"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Esconder"</string> <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_forced_resizable" msgid="7429086980048964687">"Puede que la aplicación no funcione con la pantalla dividida"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"La aplicación no es compatible con 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_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +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> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir en la parte izquierda"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir en la parte derecha"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir en la parte superior"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir en la parte inferior"</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> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No hay burbujas recientes"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Las burbujas recientes y las cerradas aparecerán aquí"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controla las burbujas cuando quieras"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toca aquí para gestionar qué aplicaciones y conversaciones pueden usar burbujas"</string> <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="6712141648865547958">"Toca para reiniciar esta aplicación y obtener una mejor vista."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Toca para reiniciar esta aplicación y obtener una mejor vista"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambiar la relación de aspecto de esta aplicación en Ajustes"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar relación de aspecto"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toca dos veces para\nmover esta aplicació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="app_icon_text" msgid="2823268023931811747">"Icono de la aplicación"</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> + <string name="select_text" msgid="5139083974039906583">"Seleccionar"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string> + <string name="close_text" msgid="4986518933445178928">"Cerrar"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Abrir menú"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index c0d0588900ad..c0558e4fc95e 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Suuruse muutmine"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Pane hoidlasse"</string> <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_forced_resizable" msgid="7429086980048964687">"Rakendus ei pruugi jagatud ekraanikuvaga töötada."</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Rakendus ei toeta jagatud ekraanikuva."</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_divider" msgid="6407584574218956849">"Jagatud ekraanikuva jaotur"</string> + <string name="divider_title" msgid="1963391955593749442">"Jagatud ekraanikuva jaotur"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ülemine: 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Ülemine: 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alumine täisekraan"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Jaga vasakule"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Jaga paremale"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Jaga üles"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Jaga alla"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ühekäerežiimi kasutamine"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Väljumiseks pühkige ekraani alaosast üles või puudutage rakenduse kohal olevat ala"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ühekäerežiimi käivitamine"</string> @@ -80,31 +76,46 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Selge"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Hiljutisi mulle pole"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Siin kuvatakse hiljutised ja suletud mullid."</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Juhtige mulle igal ajal"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Puudutage siin, et hallata, milliseid rakendusi ja vestlusi saab mullina kuvada"</string> <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="6712141648865547958">"Puudutage, et see rakendus parema vaate jaoks taaskäivitada."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Puudutage, et see rakendus parema vaate jaoks taaskäivitada"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Muutke selle rakenduse kuvasuhet jaotises Seaded"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Muutke kuvasuhet"</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="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_split_screen_text" msgid="449233070804658627">"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="letterbox_restart_dialog_title" msgid="8543049527871033505">"Kas taaskäivitada parema vaate saavutamiseks?"</string> - <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Saate rakenduse taaskäivitada, et see näeks ekraanikuval parem välja, kuid võite kaotada edenemise või salvestamata muudatused"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Saate rakenduse taaskäivitada, et see näeks ekraanikuval parem välja, kuid võite kaotada edenemise või salvestamata muudatused."</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Tühista"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Taaskäivita"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ära kuva uuesti"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Rakenduse teisaldamiseks\ntopeltpuudutage"</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="app_icon_text" msgid="2823268023931811747">"Rakenduse ikoon"</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> + <string name="select_text" msgid="5139083974039906583">"Vali"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Ekraanipilt"</string> + <string name="close_text" msgid="4986518933445178928">"Sule"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Ava menüü"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index 6a1f45727203..7610f0dc4312 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Aldatu tamaina"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Gorde"</string> <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_forced_resizable" msgid="7429086980048964687">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Pantaila-zatitzailea"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ezarri goialdea % 50en"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Ezarri goialdea % 30en"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ezarri behealdea pantaila osoan"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Zatitu ezkerraldean"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Zatitu eskuinaldean"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Zatitu goialdean"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Zatitu behealdean"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Esku bakarreko modua erabiltzea"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Irteteko, pasatu hatza pantailaren behealdetik gora edo sakatu aplikazioaren gainaldea"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Abiarazi esku bakarreko modua"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ados"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ez dago azkenaldiko burbuilarik"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Azken burbuilak eta baztertutakoak agertuko dira hemen"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrolatu burbuilak edonoiz"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Sakatu hau burbuiletan zein aplikazio eta elkarrizketa ager daitezkeen kudeatzeko"</string> <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="6712141648865547958">"Hobeto ikusteko, sakatu hau aplikazioa berrabiarazteko."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Hobeto ikusteko, sakatu hau, eta aplikazioa berrabiarazi egingo da"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Aldatu aplikazioaren aspektu-erlazioa ezarpenetan"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Aldatu aspektu-erlazioa"</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="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_split_screen_text" msgid="449233070804658627">"Pantaila zatitua 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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Utzi"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Berrabiarazi"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ez erakutsi berriro"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Sakatu birritan\naplikazioa mugitzeko"</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="app_icon_text" msgid="2823268023931811747">"Aplikazioaren ikonoa"</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> + <string name="select_text" msgid="5139083974039906583">"Hautatu"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Pantaila-argazkia"</string> + <string name="close_text" msgid="4986518933445178928">"Itxi"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Ireki menua"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index 59c119d18c2d..f1fb51fa5ece 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"ممکن است برنامه با صفحهٔ دونیمه کار نکند"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"برنامه از صفحهٔ دونیمه پشتیبانی نمیکند"</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_divider" msgid="6407584574218956849">"تقسیمکننده صفحهٔ دونیمه"</string> + <string name="divider_title" msgid="1963391955593749442">"تقسیمکننده صفحهٔ دونیمه"</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> @@ -49,21 +49,17 @@ <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> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"تقسیم از چپ"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"تقسیم از راست"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"تقسیم از بالا"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"تقسیم از پایین"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"استفاده از حالت یکدستی"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"برای خارج شدن، از پایین صفحهنمایش تند بهطرف بالا بکشید یا در هر جایی از بالای برنامه که میخواهید ضربه بزنید"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"آغاز «حالت یکدستی»"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"خروج از «حالت یکدستی»"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"تنظیمات برای حبابکهای <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> - <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"لبریزشده"</string> - <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"افزودن برگشت به پشته"</string> + <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"سرریز"</string> + <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"افزودن برگشتن به پشته"</string> <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <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> از <xliff:g id="APP_NAME">%2$s</xliff:g> و <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> مورد بیشتر"</string> <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"انتقال به بالا سمت راست"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"کنترل حبابکها در هرزمانی"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"برای مدیریت اینکه کدام برنامهها و مکالمهها حباب داشته باشند، ضربه بزنید"</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="6712141648865547958">"برای داشتن نمایی بهتر، ضربه بزنید تا این برنامه بازراهاندازی شود."</string> + <string name="restart_button_description" msgid="4564728020654658478">"برای داشتن نمایی بهتر، ضربه بزنید تا این برنامه بازراهاندازی شود"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"نسبت ابعادی این برنامه را در «تنظیمات» تغییر دهید"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تغییر نسبت ابعادی"</string> <string name="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="7739895354143295358">"از چندین برنامه بهطور همزمان استفاده کنید"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"برای حالت صفحهٔ دونیمه، در برنامهای دیگر بکشید"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"برای حالت صفحهٔ دونیمه، در برنامهای دیگر بکشید"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"لغو کردن"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"بازراهاندازی"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوباره نشان داده نشود"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"برای جابهجا کردن این برنامه\nدوضربه بزنید"</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="app_icon_text" msgid="2823268023931811747">"نماد برنامه"</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> + <string name="select_text" msgid="5139083974039906583">"انتخاب"</string> + <string name="screenshot_text" msgid="1477704010087786671">"نماگرفت"</string> + <string name="close_text" msgid="4986518933445178928">"بستن"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"باز کردن منو"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index edb1dae1269d..526925531ac0 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Muuta kokoa"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Lisää turvasäilytykseen"</string> <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_forced_resizable" msgid="7429086980048964687">"Sovellus ei ehkä toimi jaetulla näytöllä"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Näytönjakaja"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yläosa 50 %"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Yläosa 30 %"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alaosa koko näytölle"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Vasemmalla"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Oikealla"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Ylhäällä"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Alhaalla"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Yhden käden moodin käyttö"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Poistu pyyhkäisemällä ylös näytön alareunasta tai napauttamalla sovelluksen yllä"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Käynnistä yhden käden moodi"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Okei"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ei viimeaikaisia kuplia"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Viimeaikaiset ja äskettäin ohitetut kuplat näkyvät täällä"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Muuta kuplien asetuksia milloin tahansa"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Valitse napauttamalla tästä, mitkä sovellukset ja keskustelut voivat kuplia"</string> <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="6712141648865547958">"Napauta, niin sovellus käynnistyy uudelleen paremmin näytölle sopivana."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Napauta, niin sovellus käynnistyy uudelleen paremmin näytölle sopivana"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Muuta tämän sovelluksen kuvasuhdetta Asetuksissa"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Vaihda kuvasuhdetta"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Peru"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Käynnistä uudelleen"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Älä näytä uudelleen"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Kaksoisnapauta, jos\nhaluat siirtää sovellusta"</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="app_icon_text" msgid="2823268023931811747">"Sovelluskuvake"</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> + <string name="select_text" msgid="5139083974039906583">"Valitse"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Kuvakaappaus"</string> + <string name="close_text" msgid="4986518933445178928">"Sulje"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Avaa valikko"</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 ee6e7be45024..cd85f402cfab 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionner"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ajouter à la réserve"</string> <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_forced_resizable" msgid="7429086980048964687">"L\'application peut ne pas fonctionner avec l\'écran partagé"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'application ne prend pas en charge 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_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % dans le haut"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % dans le haut"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Plein écran dans le bas"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Diviser à gauche"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Diviser à droite"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Diviser dans la partie supérieure"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Diviser dans la partie inférieure"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode Une main"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'application"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode Une main"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Aucune bulle récente"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bulles récentes et les bulles ignorées s\'afficheront ici"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Gérez les bulles en tout temps"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Touchez ici pour gérer les applis et les conversations à inclure aux bulles"</string> <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="6712141648865547958">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Changer les proportions de cette application dans les paramètres"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier les proportions"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toucher deux fois pour\ndéplacer cette application"</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="app_icon_text" msgid="2823268023931811747">"Icône de l\'application"</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> + <string name="select_text" msgid="5139083974039906583">"Sélectionner"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Capture d\'écran"</string> + <string name="close_text" msgid="4986518933445178928">"Fermer"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Ouvrir le menu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index c9fc0613260b..23ba785363f7 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionner"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string> <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_forced_resizable" msgid="7429086980048964687">"L\'appli peut ne pas fonctionner en mode Écran partagé"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appli 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_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Écran du haut à 50 %"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Écran du haut à 30 %"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Écran du bas en plein écran"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Affichée à gauche"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Affichée à droite"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Affichée en haut"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Affichée en haut"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode une main"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran de bas en haut ou appuyez n\'importe où au-dessus de l\'application"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode une main"</string> @@ -80,31 +76,46 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Aucune bulle récente"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bulles récentes et ignorées s\'afficheront ici"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Contrôlez les bulles à tout moment"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Appuyez ici pour gérer les applis et conversations s\'affichant dans des bulles"</string> <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="6712141648865547958">"Appuyez pour redémarrer cette appli et avoir une meilleure vue."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Appuyez pour redémarrer cette appli et obtenir une meilleure vue."</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Modifiez le format de cette appli dans les Paramètres."</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier le format"</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="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_split_screen_text" msgid="449233070804658627">"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="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour améliorer l\'affichage ?"</string> - <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour en améliorer son aspect sur votre écran, mais vous risquez de perdre votre progression ou les modifications non enregistrées"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour un meilleur rendu sur votre écran, mais il se peut que vous perdiez votre progression ou les modifications non enregistrées"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Appuyez deux fois\npour déplacer cette appli"</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="app_icon_text" msgid="2823268023931811747">"Icône d\'application"</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> + <string name="select_text" msgid="5139083974039906583">"Sélectionner"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Capture d\'écran"</string> + <string name="close_text" msgid="4986518933445178928">"Fermer"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Ouvrir le menu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index 3e1a93fe424e..8693e42118fc 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Cambiar tamaño"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Esconder"</string> <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_forced_resizable" msgid="7429086980048964687">"É posible que a aplicación non funcione coa pantalla dividida"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"A aplicación non admite a 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_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % arriba"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % arriba"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla completa abaixo"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir (esquerda)"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir (dereita)"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir (arriba)"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir (abaixo)"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como se usa o modo dunha soa man?"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para saír, pasa o dedo cara arriba desde a parte inferior da pantalla ou toca calquera lugar da zona situada encima da aplicación"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo dunha soa man"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Non hai burbullas recentes"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"As burbullas recentes e ignoradas aparecerán aquí."</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controlar as burbullas"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toca para xestionar as aplicacións e conversas que poden aparecer en burbullas"</string> <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="6712141648865547958">"Toca o botón para reiniciar esta aplicación e gozar dunha mellor visualización."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Toca o botón para reiniciar esta aplicación e gozar dunha mellor visualización"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambia a proporción desta aplicación en Configuración"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar a proporció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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrar outra vez"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toca dúas veces para\nmover esta aplicació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="app_icon_text" msgid="2823268023931811747">"Icona de aplicación"</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> + <string name="select_text" msgid="5139083974039906583">"Seleccionar"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string> + <string name="close_text" msgid="4986518933445178928">"Pechar"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Abrir menú"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index 24950f760e5a..a7cdf7352189 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"વિભાજિત સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ઍપ વિભાજિત સ્ક્રીનને સપોર્ટ કરતી નથી"</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_divider" msgid="6407584574218956849">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string> + <string name="divider_title" msgid="1963391955593749442">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"શીર્ષ 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"શીર્ષ 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"તળિયાની પૂર્ણ સ્ક્રીન"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"ડાબે વિભાજિત કરો"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"જમણે વિભાજિત કરો"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"ઉપર વિભાજિત કરો"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"નીચે વિભાજિત કરો"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"એક-હાથે વાપરો મોડનો ઉપયોગ કરી રહ્યાં છીએ"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"બહાર નીકળવા માટે, સ્ક્રીનની નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરો અથવા ઍપના આઇકન પર ગમે ત્યાં ટૅપ કરો"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"એક-હાથે વાપરો મોડ શરૂ કરો"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"બબલને કોઈપણ સમયે નિયંત્રિત કરે છે"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"કઈ ઍપ અને વાતચીતોને બબલ કરવા માગો છો તે મેનેજ કરવા માટે, અહીં ટૅપ કરો"</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="6712141648865547958">"વધુ સારા વ્યૂ માટે, આ ઍપને ફરી શરૂ કરવા ટૅપ કરો."</string> + <string name="restart_button_description" msgid="4564728020654658478">"વધુ સારા વ્યૂ માટે, આ ઍપને ફરી શરૂ કરવા ટૅપ કરો"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"સેટિંગમાં આ ઍપનો સાપેક્ષ ગુણોત્તર બદલો"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"સાપેક્ષ ગુણોત્તર બદલો"</string> <string name="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="7739895354143295358">"જુઓ અને બીજું ઘણું કરો"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"સ્ક્રીન વિભાજન માટે કોઈ અન્ય ઍપમાં ખેંચો"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"વિભાજિત સ્ક્રીન માટે કોઈ અન્ય ઍપમાં ખેંચો"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"રદ કરો"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"ફરી શરૂ કરો"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ફરીથી બતાવશો નહીં"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"આ ઍપને ખસેડવા માટે\nબે વાર ટૅપ કરો"</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="app_icon_text" msgid="2823268023931811747">"ઍપનું આઇકન"</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> + <string name="select_text" msgid="5139083974039906583">"પસંદ કરો"</string> + <string name="screenshot_text" msgid="1477704010087786671">"સ્ક્રીનશૉટ"</string> + <string name="close_text" msgid="4986518933445178928">"બંધ કરો"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"મેનૂ ખોલો"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index 9f36799092e1..13e0258ce87b 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"मुमकिन है कि ऐप्लिकेशन, स्प्लिट स्क्रीन मोड में काम न करे"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"यह ऐप्लिकेशन, स्प्लिट स्क्रीन मोड पर काम नहीं करता"</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_divider" msgid="6407584574218956849">"स्प्लिट स्क्रीन डिवाइडर मोड"</string> + <string name="divider_title" msgid="1963391955593749442">"स्प्लिट स्क्रीन डिवाइडर मोड"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ऊपर की स्क्रीन को 50% बनाएं"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ऊपर की स्क्रीन को 30% बनाएं"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"नीचे की स्क्रीन को फ़ुल स्क्रीन बनाएं"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"स्क्रीन को बाएं हिस्से में स्प्लिट करें"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"स्क्रीन को दाएं हिस्से में स्प्लिट करें"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"स्क्रीन को ऊपर के हिस्से में स्प्लिट करें"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"स्क्रीन को सबसे नीचे वाले हिस्से में स्प्लिट करें"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"वन-हैंडेड मोड का इस्तेमाल करना"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"इस मोड से बाहर निकलने के लिए, स्क्रीन के सबसे निचले हिस्से से ऊपर की ओर स्वाइप करें या ऐप्लिकेशन के बाहर कहीं भी टैप करें"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"वन-हैंडेड मोड चालू करें"</string> @@ -80,31 +76,46 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जब चाहें, बबल्स की सुविधा को कंट्रोल करें"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"किसी ऐप्लिकेशन और बातचीत के लिए बबल की सुविधा को मैनेज करने के लिए यहां टैप करें"</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="6712141648865547958">"टैप करके ऐप्लिकेशन को रीस्टार्ट करें और बेहतर व्यू पाएं."</string> + <string name="restart_button_description" msgid="4564728020654658478">"बेहतर व्यू पाने के लिए, टैप करके ऐप्लिकेशन को रीस्टार्ट करें"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"सेटिंग में जाकर इस ऐप्लिकेशन का आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें"</string> <string name="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="7739895354143295358">"पूरी जानकारी लेकर, बेहतर तरीके से काम करें"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रीन के लिए, दूसरे ऐप्लिकेशन को खींचें और छोड़ें"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रीन का इस्तेमाल करने के लिए, किसी अन्य ऐप्लिकेशन को खींचें और छोड़ें"</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="letterbox_restart_dialog_title" msgid="8543049527871033505">"बेहतर व्यू पाने के लिए ऐप्लिकेशन को रीस्टार्ट करना है?"</string> - <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"स्क्रीन पर ऐप्लिकेशन का बेहतर व्यू पाने के लिए उसे रीस्टार्ट करें. हालांकि, आपने जो बदलाव सेव नहीं किए हैं या अब तक जो काम किए हैं उनका डेटा, ऐप्लिकेशन रीस्टार्ट करने पर मिट सकता है"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"स्क्रीन पर ऐप्लिकेशन का बेहतर व्यू पाने के लिए उसे रीस्टार्ट करें. हालांकि, इससे अब तक किया गया काम और सेव न किए गए बदलाव मिट सकते हैं"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द करें"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करें"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फिर से न दिखाएं"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ऐप्लिकेशन की जगह बदलने के लिए\nदो बार टैप करें"</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="app_icon_text" msgid="2823268023931811747">"ऐप्लिकेशन आइकॉन"</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> + <string name="select_text" msgid="5139083974039906583">"चुनें"</string> + <string name="screenshot_text" msgid="1477704010087786671">"स्क्रीनशॉट"</string> + <string name="close_text" msgid="4986518933445178928">"बंद करें"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"मेन्यू खोलें"</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 595435bcf8b8..e2f272c36329 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml @@ -18,7 +18,7 @@ <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="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई शीर्षक कार्यक्रम नहीं)"</string> + <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई टाइटल कार्यक्रम नहीं)"</string> <string name="pip_close" msgid="2955969519031223530">"बंद करें"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"फ़ुल स्क्रीन"</string> <string name="pip_move" msgid="158770205886688553">"ले जाएं"</string> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index 9459e4c5f2af..957e56c35e87 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Promjena veličine"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Sakrijte"</string> <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_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Razdjelnik podijeljenog zaslona"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji zaslon na 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gornji zaslon na 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Donji zaslon u cijeli zaslon"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Podijeli lijevo"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Podijeli desno"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Podijeli gore"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podijeli dolje"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korištenje načina rada jednom rukom"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Za izlaz prijeđite prstom od dna zaslona prema gore ili dodirnite bio gdje iznad aplikacije"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pokretanje načina rada jednom rukom"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Shvaćam"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nema nedavnih oblačića"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Ovdje će se prikazivati nedavni i odbačeni oblačići"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljanje oblačićima u svakom trenutku"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovdje da biste odredili koje aplikacije i razgovori mogu imati oblačić"</string> <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="6712141648865547958">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promijeni omjer slike ove aplikacije u postavkama"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promijeni omjer slike"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Odustani"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Pokreni ponovno"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovno"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvaput dodirnite da biste\npremjestili ovu aplikaciju"</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="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</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> + <string name="select_text" msgid="5139083974039906583">"Odaberite"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Snimka zaslona"</string> + <string name="close_text" msgid="4986518933445178928">"Zatvorite"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Otvaranje izbornika"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index fc9341b82c36..e9808ac6c2be 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Átméretezés"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Félretevés"</string> <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_forced_resizable" msgid="7429086980048964687">"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="2733543750291266047">"Az alkalmazás nem támogatja az osztott képernyőt"</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_divider" msgid="6407584574218956849">"Elválasztó az osztott képernyős nézetben"</string> + <string name="divider_title" msgid="1963391955593749442">"Elválasztó az osztott képernyős 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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Felső 50%-ra"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Felső 30%-ra"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alsó teljes képernyőre"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Osztás a képernyő bal oldalán"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Osztás a képernyő jobb oldalán"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Osztás a képernyő tetején"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Osztás alul"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Egykezes mód használata"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"A kilépéshez csúsztasson felfelé a képernyő aljáról, vagy koppintson az alkalmazás felett a képernyő bármelyik részére"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Egykezes mód indítása"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Értem"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nincsenek buborékok a közelmúltból"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"A legutóbbi és az elvetett buborékok itt jelennek majd meg"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Buborékok vezérlése bármikor"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ide koppintva jeleníthetők meg az alkalmazások és a beszélgetések buborékként"</string> <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="6712141648865547958">"A jobb nézet érdekében koppintson az alkalmazás újraindításához."</string> + <string name="restart_button_description" msgid="4564728020654658478">"A jobb nézet érdekében koppintson az alkalmazás újraindításához."</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Az app méretarányát a Beállításokban módosíthatja"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Méretarány módosítása"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Mégse"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Újraindítás"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne jelenjen meg többé"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Koppintson duplán\naz alkalmazás áthelyezéséhez"</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="app_icon_text" msgid="2823268023931811747">"Alkalmazásikon"</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> + <string name="select_text" msgid="5139083974039906583">"Kiválasztás"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Képernyőkép"</string> + <string name="close_text" msgid="4986518933445178928">"Bezárás"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Menü megnyitása"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 6943532c17be..8a9d89bd879f 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Հավելվածը չի աջակցում էկրանի տրոհումը"</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_divider" msgid="6407584574218956849">"Տրոհված էկրանի բաժանիչ"</string> + <string name="divider_title" msgid="1963391955593749442">"Տրոհված էկրանի բաժանիչ"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Վերևի էկրանը՝ 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Վերևի էկրանը՝ 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ներքևի էկրանը՝ լիաէկրան"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Հավելվածը ձախ կողմում"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Հավելվածը աջ կողմում"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Հավելվածը վերևում"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Հավելվածը ներքևում"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ինչպես օգտվել մեկ ձեռքի ռեժիմից"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Դուրս գալու համար մատը սահեցրեք էկրանի ներքևից վերև կամ հպեք հավելվածի վերևում որևէ տեղ։"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Գործարկել մեկ ձեռքի ռեժիմը"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Ամպիկների կարգավորումներ"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Հպեք այստեղ՝ ընտրելու, թե որ հավելվածների և զրույցների համար ամպիկներ ցուցադրել"</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="6712141648865547958">"Հպեք՝ հավելվածը վերագործարկելու և ավելի հարմար տեսք ընտրելու համար։"</string> + <string name="restart_button_description" msgid="4564728020654658478">"Հպեք՝ հավելվածը վերագործարկելու և ավելի հարմար տեսք ընտրելու համար"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Փոխել հավելվածի կողմերի հարաբերակցությունը Կարգավորումներում"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Փոխել չափերի հարաբերակցությունը"</string> <string name="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="7739895354143295358">"Միաժամանակ կատարեք մի քանի առաջադրանք"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Չեղարկել"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Վերագործարկել"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Այլևս ցույց չտալ"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Կրկնակի հպեք՝\nհավելվածը տեղափոխելու համար"</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="app_icon_text" msgid="2823268023931811747">"Հավելվածի պատկերակ"</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> + <string name="select_text" msgid="5139083974039906583">"Ընտրել"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Սքրինշոթ"</string> + <string name="close_text" msgid="4986518933445178928">"Փակել"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Բացել ընտրացանկը"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index 4fca32dfc812..6b84a1d9bdac 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ubah ukuran"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string> <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_forced_resizable" msgid="7429086980048964687">"Aplikasi mungkin tidak berfungsi dengan layar terpisah"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikasi 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_divider" msgid="6407584574218956849">"Pembagi layar terpisah"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Atas 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Layar penuh di bawah"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Pisahkan ke kiri"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Pisahkan ke kanan"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Pisahkan ke atas"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Pisahkan ke bawah"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Menggunakan mode satu tangan"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Untuk keluar, geser layar dari bawah ke atas atau ketuk di mana saja di atas aplikasi"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Mulai mode satu tangan"</string> @@ -80,31 +76,46 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Oke"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Tidak ada balon baru-baru ini"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Balon yang baru dipakai dan balon yang telah ditutup akan muncul di sini"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrol balon kapan saja"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ketuk di sini untuk mengelola balon aplikasi dan percakapan"</string> <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="6712141648865547958">"Ketuk untuk memulai ulang aplikasi ini agar mendapatkan tampilan yang lebih baik."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Ketuk untuk memulai ulang aplikasi ini agar mendapatkan tampilan yang lebih baik"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ubah rasio aspek aplikasi ini di Setelan"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ubah rasio aspek"</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="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_split_screen_text" msgid="449233070804658627">"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="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulai ulang untuk tampilan yang lebih baik?"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulai ulang untuk melihat tampilan yang lebih baik?"</string> <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Anda dapat memulai ulang aplikasi agar terlihat lebih baik di layar, tetapi Anda mungkin kehilangan progres atau perubahan yang belum disimpan"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulai ulang"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tampilkan lagi"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ketuk dua kali untuk\nmemindahkan aplikasi ini"</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="app_icon_text" msgid="2823268023931811747">"Ikon Aplikasi"</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> + <string name="select_text" msgid="5139083974039906583">"Pilih"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Tutup"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Buka Menu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index 1bc019e7f67e..913e1964c4df 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Breyta stærð"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Geymsla"</string> <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_forced_resizable" msgid="7429086980048964687">"Forritið virkar hugsanlega ekki með skjáskiptingu"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Forritið styður ekki skjáskiptingu"</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_divider" msgid="6407584574218956849">"Skilrúm skjáskiptingar"</string> + <string name="divider_title" msgid="1963391955593749442">"Skilrúm skjáskiptingar"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Efri 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Efri 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Neðri á öllum skjánum"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Skipta vinstra megin"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Skipta hægra megin"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Skipta efst"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Skipta neðst"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Notkun einhentrar stillingar"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Til að loka skaltu strjúka upp frá neðri hluta skjásins eða ýta hvar sem er fyrir ofan forritið"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ræsa einhenta stillingu"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ég skil"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Engar nýlegar blöðrur"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nýlegar blöðrur og blöðrur sem þú hefur lokað birtast hér"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Hægt er að stjórna blöðrum hvenær sem er"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ýttu hér til að stjórna því hvaða forrit og samtöl mega nota blöðrur."</string> <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="6712141648865547958">"Ýta til að endurræsa forritið og fá betri sýn."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Ýttu til að endurræsa forritið og fá betri sýn"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Breyta myndhlutfalli þessa forrits í stillingunum"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Breyta myndhlutfalli"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Hætta við"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Endurræsa"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ekki sýna þetta aftur"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ýttu tvisvar til\nað færa þetta forrit"</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="app_icon_text" msgid="2823268023931811747">"Tákn forrits"</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> + <string name="select_text" msgid="5139083974039906583">"Velja"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Skjámynd"</string> + <string name="close_text" msgid="4986518933445178928">"Loka"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Opna valmynd"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index 8d2715a181de..575210bffc7d 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ridimensiona"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Accantona"</string> <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_forced_resizable" msgid="7429086980048964687">"L\'app potrebbe non funzionare con lo schermo diviso"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Strumento per schermo diviso"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Schermata superiore al 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Schermata superiore al 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Schermata inferiore a schermo intero"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Dividi a sinistra"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Dividi a destra"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Dividi in alto"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividi in basso"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usare la modalità a una mano"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Per uscire, scorri verso l\'alto dalla parte inferiore dello schermo oppure tocca un punto qualsiasi sopra l\'app"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Avvia la modalità a una mano"</string> @@ -80,31 +76,46 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nessuna bolla recente"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Le bolle recenti e ignorate appariranno qui"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Gestisci le bolle in qualsiasi momento"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tocca qui per gestire le app e le conversazioni per cui mostrare le bolle"</string> <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="6712141648865547958">"Tocca per riavviare quest\'app per una migliore visualizzazione."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tocca per riavviare l\'app e migliorare la visualizzazione"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambia le proporzioni dell\'app nelle Impostazioni"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambia proporzioni"</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="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_split_screen_text" msgid="449233070804658627">"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_got_it" msgid="4057634570866051177">"Ok"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string> <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vuoi riavviare per migliorare la visualizzazione?"</string> <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puoi riavviare l\'app affinché venga visualizzata meglio sullo schermo, ma potresti perdere i tuoi progressi o eventuali modifiche non salvate"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annulla"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Riavvia"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrare più"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tocca due volte per\nspostare questa app"</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="app_icon_text" msgid="2823268023931811747">"Icona dell\'app"</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> + <string name="select_text" msgid="5139083974039906583">"Seleziona"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Chiudi"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Apri menu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index 26f3236e5947..fbc384f1be67 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"יכול להיות שהאפליקציה לא תפעל עם מסך מפוצל"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"האפליקציה לא תומכת במסך מפוצל"</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_divider" msgid="6407584574218956849">"מחלק מסך מפוצל"</string> + <string name="divider_title" msgid="1963391955593749442">"מחלק מסך מפוצל"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"עליון 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"למעלה 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"מסך תחתון מלא"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"פיצול שמאלה"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"פיצול ימינה"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"פיצול למעלה"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"פיצול למטה"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"איך להשתמש בתכונה \'מצב שימוש ביד אחת\'"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"כדי לצאת, יש להחליק למעלה מתחתית המסך או להקיש במקום כלשהו במסך מעל האפליקציה"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"הפעלה של מצב שימוש ביד אחת"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"שליטה בבועות בכל זמן"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"אפשר להקיש כאן כדי לקבוע אילו אפליקציות ושיחות יוכלו להופיע בבועות"</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="6712141648865547958">"כדי לראות טוב יותר יש להקיש ולהפעיל את האפליקציה הזו מחדש."</string> + <string name="restart_button_description" msgid="4564728020654658478">"כדי לראות טוב יותר יש להקיש ולהפעיל את האפליקציה הזו מחדש"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"אפשר לשנות את יחס הגובה-רוחב של האפליקציה הזו ב\'הגדרות\'"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"שינוי יחס גובה-רוחב"</string> <string name="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="7739895354143295358">"רוצה לראות ולעשות יותר?"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך מפוצל"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך המפוצל"</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> @@ -96,15 +100,22 @@ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"אפשר להפעיל מחדש את האפליקציה כדי שהיא תוצג באופן טוב יותר במסך, אבל ייתכן שההתקדמות שלך או כל שינוי שלא נשמר יאבדו"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ביטול"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"הפעלה מחדש"</string> - <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"אין צורך להציג את זה שוב"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"אין להציג שוב"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"אפשר להקיש הקשה כפולה כדי\nלהעביר את האפליקציה למקום אחר"</string> <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string> <string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string> <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="app_icon_text" msgid="2823268023931811747">"סמל האפליקציה"</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> + <string name="select_text" msgid="5139083974039906583">"בחירה"</string> + <string name="screenshot_text" msgid="1477704010087786671">"צילום מסך"</string> + <string name="close_text" msgid="4986518933445178928">"סגירה"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"פתיחת התפריט"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index 91fd724791fa..dce3a18f8cd0 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"アプリは分割画面では動作しないことがあります"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"アプリで分割画面がサポートされていません"</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_divider" msgid="6407584574218956849">"分割画面の分割線"</string> + <string name="divider_title" msgid="1963391955593749442">"分割画面の分割線"</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> @@ -49,16 +49,12 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"上 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"上 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"下部全画面"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"左に分割"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"右に分割"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"上に分割"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"下に分割"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"片手モードの使用"</string> - <string name="one_handed_tutorial_description" msgid="3486582858591353067">"終了するには、画面を下から上にスワイプするか、アプリの任意の場所をタップします"</string> + <string name="one_handed_tutorial_description" msgid="3486582858591353067">"終了するには、画面を下から上にスワイプするか、アプリの上側の任意の場所をタップします"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"片手モードを開始します"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"片手モードを終了します"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> のバブルの設定"</string> @@ -80,31 +76,46 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近閉じたバブルはありません"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"最近表示されたバブルや閉じたバブルが、ここに表示されます"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"バブルはいつでも管理可能"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"バブルで表示するアプリや会話を管理するには、ここをタップします"</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="6712141648865547958">"タップしてこのアプリを再起動すると、表示が適切になります。"</string> + <string name="restart_button_description" msgid="4564728020654658478">"タップしてこのアプリを再起動すると、表示が適切になります"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"このアプリのアスペクト比を [設定] で変更します"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"アスペクト比を変更"</string> <string name="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="7739895354143295358">"表示を拡大して機能を強化"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"分割画面にするにはもう 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="letterbox_restart_dialog_title" msgid="8543049527871033505">"再起動して画面をすっきりさせますか?"</string> - <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"アプリを再起動して画面をすっきりさせることはできますが、進捗状況が失われ、保存されていない変更が消える可能性があります"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"アプリを再起動して画面表示を最適化しますか?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"アプリを再起動することにより表示を最適化できますが、保存されていない変更は失われる可能性があります"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"キャンセル"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"再起動"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"次回から表示しない"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ダブルタップすると\nこのアプリを移動できます"</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="app_icon_text" msgid="2823268023931811747">"アプリのアイコン"</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> + <string name="select_text" msgid="5139083974039906583">"選択"</string> + <string name="screenshot_text" msgid="1477704010087786671">"スクリーンショット"</string> + <string name="close_text" msgid="4986518933445178928">"閉じる"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"メニューを開く"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index a711afd5e39e..b396c8c89046 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ"</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_divider" msgid="6407584574218956849">"ეკრანის გაყოფის გამყოფი"</string> + <string name="divider_title" msgid="1963391955593749442">"ეკრანის გაყოფის გამყოფი"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ზედა ეკრანი — 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ზედა ეკრანი — 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ქვედა ნაწილის სრულ ეკრანზე გაშლა"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"გაყოფა მარცხნივ"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"გაყოფა მარჯვნივ"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"გაყოფა ზემოთ"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"გაყოფა ქვემოთ"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ცალი ხელის რეჟიმის გამოყენება"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"გასასვლელად გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ ან შეეხეთ ნებისმიერ ადგილას აპის ზემოთ"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ცალი ხელის რეჟიმის დაწყება"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ამოხტომის გაკონტროლება ნებისმიერ დროს"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"აქ შეეხეთ იმის სამართავად, თუ რომელი აპები და საუბრები ამოხტეს"</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="6712141648865547958">"შეეხეთ, რომ გადატვირთოთ ეს აპი უკეთესი ხედისთვის."</string> + <string name="restart_button_description" msgid="4564728020654658478">"შეხებით გადატვირთეთ ეს აპი უკეთესი ხედის მისაღებად"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"შეცვალეთ ამ აპის თანაფარდობა პარამეტრებიდან"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"თანაფარდობის შეცვლა"</string> <string name="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="7739895354143295358">"მეტის ნახვა და გაკეთება"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"გაუქმება"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"გადატვირთვა"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"აღარ გამოჩნდეს"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ამ აპის გადასატანად\nორმაგად შეეხეთ მას"</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="app_icon_text" msgid="2823268023931811747">"აპის ხატულა"</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> + <string name="select_text" msgid="5139083974039906583">"არჩევა"</string> + <string name="screenshot_text" msgid="1477704010087786671">"ეკრანის ანაბეჭდი"</string> + <string name="close_text" msgid="4986518933445178928">"დახურვა"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"მენიუს გახსნა"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index a2e868805d08..63ef3d2000dd 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Қолданбада экранды бөлу мүмкін емес."</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_divider" msgid="6407584574218956849">"Экранды бөлу режимінің бөлгіші"</string> + <string name="divider_title" msgid="1963391955593749442">"Экранды бөлу режимінің бөлгіші"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% жоғарғы жақта"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% жоғарғы жақта"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Төменгісін толық экранға шығару"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Сол жақтан шығару"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Оң жақтан шығару"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Жоғарыдан шығару"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Астынан шығару"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Бір қолмен енгізу режимін пайдалану"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Шығу үшін экранның төменгі жағынан жоғары қарай сырғытыңыз немесе қолданбаның үстінен кез келген жерден түртіңіз."</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бір қолмен енгізу режимін іске қосу"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Қалқыма хабарларды кез келген уақытта басқарыңыз"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Қалқыма хабарда көрсетілетін қолданбалар мен әңгімелерді реттеу үшін осы жерді түртіңіз."</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="6712141648865547958">"Ыңғайлы көріністі реттеу үшін қолданбаны түртіп, өшіріп қосыңыз."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Көріністі жақсарту үшін осы қолданбаны түртіп, қайта ашыңыз."</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Осы қолданбаның арақатынасын параметрлерден өзгертуге болады."</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Арақатынасты өзгерту"</string> <string name="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="7739895354143295358">"Қосымша ақпаратты қарап, әрекеттер жасау"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлу үшін басқа қолданбаға сүйреңіз."</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Экранды бөлу үшін басқа қолданбаға өтіңіз."</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Бас тарту"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өшіріп қосу"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Қайта көрсетілмесін"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Бұл қолданбаны басқа орынға\nжылжыту үшін екі рет түртіңіз."</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="app_icon_text" msgid="2823268023931811747">"Қолданба белгішесі"</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> + <string name="select_text" msgid="5139083974039906583">"Таңдау"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string> + <string name="close_text" msgid="4986518933445178928">"Жабу"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Мәзірді ашу"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 7a486b86747c..2ce8ba37b3bf 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"កម្មវិធីអាចមិនដំណើរការជាមួយមុខងារបំបែកអេក្រង់ទេ"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"កម្មវិធីមិនអាចប្រើមុខងារបំបែកអេក្រង់បានទេ"</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_divider" msgid="6407584574218956849">"បន្ទាត់ខណ្ឌចែកក្នុងមុខងារបំបែកអេក្រង់"</string> + <string name="divider_title" msgid="1963391955593749442">"បន្ទាត់ខណ្ឌចែកក្នុងមុខងារបំបែកអេក្រង់"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ខាងលើ 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ខាងលើ 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"អេក្រង់ពេញខាងក្រោម"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"បំបែកខាងឆ្វេង"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"បំបែកខាងស្ដាំ"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"បំបែកខាងលើ"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"បំបែកខាងក្រោម"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"កំពុងប្រើមុខងារប្រើដៃម្ខាង"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ដើម្បីចាកចេញ សូមអូសឡើងលើពីផ្នែកខាងក្រោមអេក្រង់ ឬចុចផ្នែកណាមួយនៅខាងលើកម្មវិធី"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ចាប់ផ្ដើមមុខងារប្រើដៃម្ខាង"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"គ្រប់គ្រងផ្ទាំងអណ្ដែតនៅពេលណាក៏បាន"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ចុចត្រង់នេះ ដើម្បីគ្រប់គ្រងកម្មវិធី និងការសន្ទនាដែលអាចបង្ហាញជាផ្ទាំងអណ្ដែត"</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="6712141648865547958">"ចុចដើម្បីចាប់ផ្ដើមកម្មវិធីនេះឡើងវិញសម្រាប់ទិដ្ឋភាពកាន់តែប្រសើរ។"</string> + <string name="restart_button_description" msgid="4564728020654658478">"ចុចដើម្បីចាប់ផ្ដើមកម្មវិធីនេះឡើងវិញសម្រាប់ទិដ្ឋភាពកាន់តែប្រសើរ"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ផ្លាស់ប្ដូរសមាមាត្ររបស់កម្មវិធីនេះនៅក្នុងការកំណត់"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ប្ដូរសមាមាត្រ"</string> <string name="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="7739895354143295358">"មើលឃើញ និងធ្វើបានកាន់តែច្រើន"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"អូសកម្មវិធីមួយទៀតចូល ដើម្បីប្រើមុខងារបំបែកអេក្រង់"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"អូសកម្មវិធីមួយទៀតចូល ដើម្បីប្រើមុខងារបំបែកអេក្រង់"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"បោះបង់"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"ចាប់ផ្ដើមឡើងវិញ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"កុំបង្ហាញម្ដងទៀត"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ចុចពីរដងដើម្បី\nផ្លាស់ទីកម្មវិធីនេះ"</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="app_icon_text" msgid="2823268023931811747">"រូបកម្មវិធី"</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> + <string name="select_text" msgid="5139083974039906583">"ជ្រើសរើស"</string> + <string name="screenshot_text" msgid="1477704010087786671">"រូបថតអេក្រង់"</string> + <string name="close_text" msgid="4986518933445178928">"បិទ"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"បិទម៉ឺនុយ"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"បើកម៉ឺនុយ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index 5fcdcf2c8fac..4b8aaa93b2a7 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -24,7 +24,7 @@ <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_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> @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಆ್ಯಪ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</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_divider" msgid="6407584574218956849">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"</string> + <string name="divider_title" msgid="1963391955593749442">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% ಮೇಲಕ್ಕೆ"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% ಮೇಲಕ್ಕೆ"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ಕೆಳಗಿನ ಪೂರ್ಣ ಪರದೆ"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"ಎಡಕ್ಕೆ ವಿಭಜಿಸಿ"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"ಬಲಕ್ಕೆ ವಿಭಜಿಸಿ"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"ಮೇಲಕ್ಕೆ ವಿಭಜಿಸಿ"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"ಕೆಳಕ್ಕೆ ವಿಭಜಿಸಿ"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ಒಂದು ಕೈ ಮೋಡ್ ಬಳಸುವುದರ ಬಗ್ಗೆ"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ನಿರ್ಗಮಿಸಲು, ಸ್ಕ್ರೀನ್ನ ಕೆಳಗಿನಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ ಅಥವಾ ಆ್ಯಪ್ನ ಮೇಲೆ ಎಲ್ಲಿಯಾದರೂ ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ಒಂದು ಕೈ ಮೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ಯಾವುದೇ ಸಮಯದಲ್ಲಿ ಬಬಲ್ಸ್ ಅನ್ನು ನಿಯಂತ್ರಿಸಿ"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ಯಾವ ಆ್ಯಪ್ಗಳು ಮತ್ತು ಸಂಭಾಷಣೆಗಳನ್ನು ಬಬಲ್ ಮಾಡಬಹುದು ಎಂಬುದನ್ನು ನಿರ್ವಹಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</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="6712141648865547958">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string> + <string name="restart_button_description" msgid="4564728020654658478">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಈ ಆ್ಯಪ್ನ ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ"</string> <string name="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="7739895354143295358">"ನೋಡಿ ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಿ"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್ನಲ್ಲಿ ಎಳೆಯಿರಿ"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್ನಲ್ಲಿ ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ರದ್ದುಮಾಡಿ"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"ಮರುಪ್ರಾರಂಭಿಸಿ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಸರಿಸಲು\nಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</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="app_icon_text" msgid="2823268023931811747">"ಆ್ಯಪ್ ಐಕಾನ್"</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> + <string name="select_text" msgid="5139083974039906583">"ಆಯ್ಕೆಮಾಡಿ"</string> + <string name="screenshot_text" msgid="1477704010087786671">"ಸ್ಕ್ರೀನ್ಶಾಟ್"</string> + <string name="close_text" msgid="4986518933445178928">"ಮುಚ್ಚಿ"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"ಮೆನು ತೆರೆಯಿರಿ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index 74cff1f5b13e..ffa77b0dc0e2 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"앱이 화면 분할 모드로는 작동하지 않을 수 있습니다"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"앱이 화면 분할을 지원하지 않습니다"</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_divider" msgid="6407584574218956849">"화면 분할기"</string> + <string name="divider_title" msgid="1963391955593749442">"화면 분할기"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"위쪽 화면 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"위쪽 화면 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"아래쪽 화면 전체화면"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"왼쪽으로 분할"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"오른쪽으로 분할"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"위쪽으로 분할"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"아래쪽으로 분할"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"한 손 사용 모드 사용하기"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"화면 하단에서 위로 스와이프하거나 앱 상단을 탭하여 종료합니다."</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"한 손 사용 모드 시작"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"언제든지 대화창을 제어하세요"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"대화창을 만들 수 있는 앱과 대화를 관리하려면 여기를 탭하세요."</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="6712141648865547958">"보기를 개선하려면 탭하여 앱을 다시 시작합니다."</string> + <string name="restart_button_description" msgid="4564728020654658478">"탭하면 앱을 다시 시작하여 보기를 개선합니다."</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"설정에서 앱의 가로세로 비율을 변경합니다."</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"가로세로 비율 변경"</string> <string name="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="7739895354143295358">"더 많은 정보를 보고 더 많은 작업을 처리하세요"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"취소"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"다시 시작"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"다시 표시 안함"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"두 번 탭하여\n이 앱 이동"</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="app_icon_text" msgid="2823268023931811747">"앱 아이콘"</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> + <string name="select_text" msgid="5139083974039906583">"선택"</string> + <string name="screenshot_text" msgid="1477704010087786671">"스크린샷"</string> + <string name="close_text" msgid="4986518933445178928">"닫기"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"메뉴 열기"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 477c65d2e68e..b74875c5885a 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -24,7 +24,7 @@ <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_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> @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Колдонмодо экран бөлүнбөшү мүмкүн"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Колдонмодо экран бөлүнбөйт"</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_divider" msgid="6407584574218956849">"Экранды бөлгүч"</string> + <string name="divider_title" msgid="1963391955593749442">"Экранды бөлгүч"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Үстүнкү экранды 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Үстүнкү экранды 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ылдыйкы экранды толук экран режимине өткөрүү"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Солго бөлүү"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Оңго бөлүү"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Өйдө бөлүү"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Ылдый бөлүү"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Бир кол режимин колдонуу"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чыгуу үчүн экранды ылдый жагынан өйдө сүрүңүз же колдонмонун өйдө жагын басыңыз"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бир кол режимин баштоо"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Калкып чыкма билдирмелерди каалаган убакта көзөмөлдөңүз"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Калкып чыкма билдирме түрүндө көрүнө турган колдонмолор менен маектерди тандоо үчүн бул жерди таптаңыз"</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="6712141648865547958">"Жакшыраак көрүү үчүн бул колдонмону өчүрүп күйгүзүңүз. Ал үчүн таптап коюңуз."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Жакшыраак көрүү үчүн бул колдонмону өчүрүп күйгүзүңүз. Ал үчүн таптап коюңуз"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Бул колдонмонун тараптарынын катнашын параметрлерден өзгөртүү"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Тараптардын катнашын өзгөртүү"</string> <string name="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="7739895354143295358">"Көрүп, көбүрөөк нерселерди жасаңыз"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Токтотуу"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өчүрүп күйгүзүү"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Экинчи көрүнбөсүн"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Бул колдонмону жылдыруу үчүн\nэки жолу таптаңыз"</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="app_icon_text" msgid="2823268023931811747">"Колдонмонун сүрөтчөсү"</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> + <string name="select_text" msgid="5139083974039906583">"Тандоо"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string> + <string name="close_text" msgid="4986518933445178928">"Жабуу"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Менюну ачуу"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index 81b082600f76..3e1ab6d28eeb 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບໂໝດແບ່ງໜ້າຈໍ"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ແອັບບໍ່ຮອງຮັບການແບ່ງໜ້າຈໍ"</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_divider" msgid="6407584574218956849">"ເສັ້ນແບ່ງໜ້າຈໍ"</string> + <string name="divider_title" msgid="1963391955593749442">"ເສັ້ນແບ່ງໜ້າຈໍ"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ເທິງສຸດ 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ເທິງສຸດ 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ເຕັມໜ້າຈໍລຸ່ມສຸດ"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"ແຍກຊ້າຍ"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"ແຍກຂວາ"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"ແຍກເທິງ"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"ແຍກລຸ່ມ"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ກຳລັງໃຊ້ໂໝດມືດຽວ"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ເພື່ອອອກ, ໃຫ້ປັດຂຶ້ນຈາກລຸ່ມສຸດຂອງໜ້າຈໍ ຫຼື ແຕະບ່ອນໃດກໍໄດ້ຢູ່ເໜືອແອັບ"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ເລີ່ມໂໝດມືດຽວ"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ຄວບຄຸມຟອງໄດ້ທຸກເວລາ"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ແຕະບ່ອນນີ້ເພື່ອຈັດການແອັບ ແລະ ການສົນທະນາທີ່ສາມາດສະແດງເປັນແບບຟອງໄດ້"</string> <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="6712141648865547958">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ເພື່ອມຸມມອງທີ່ດີຂຶ້ນ."</string> + <string name="restart_button_description" msgid="4564728020654658478">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ເພື່ອມຸມມອງທີ່ດີຂຶ້ນ"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ປ່ຽນອັດຕາສ່ວນຂອງແອັບນີ້ໃນການຕັ້ງຄ່າ"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ປ່ຽນອັດຕາສ່ວນ"</string> <string name="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="7739895354143295358">"ເບິ່ງ ແລະ ເຮັດຫຼາຍຂຶ້ນ"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ລາກແອັບອື່ນເຂົ້າມາເພື່ອແບ່ງໜ້າຈໍ"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ລາກໄປໄວ້ໃນແອັບອື່ນເພື່ອແບ່ງໜ້າຈໍ"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ຍົກເລີກ"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"ຣີສະຕາດ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ບໍ່ຕ້ອງສະແດງອີກ"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ແຕະສອງເທື່ອເພື່ອ\nຍ້າຍແອັບນີ້"</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="app_icon_text" msgid="2823268023931811747">"ໄອຄອນແອັບ"</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> + <string name="select_text" msgid="5139083974039906583">"ເລືອກ"</string> + <string name="screenshot_text" msgid="1477704010087786671">"ຮູບໜ້າຈໍ"</string> + <string name="close_text" msgid="4986518933445178928">"ປິດ"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"ເປີດເມນູ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index 0447ec7b2797..f4751aae323d 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Pakeisti dydį"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Paslėpti"</string> <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_forced_resizable" msgid="7429086980048964687">"Programa gali neveikti naudojant išskaidyto ekrano režimą"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Programoje nepalaikomas išskaidyto ekrano režimas"</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_divider" msgid="6407584574218956849">"Išskaidyto ekrano režimo daliklis"</string> + <string name="divider_title" msgid="1963391955593749442">"Išskaidyto ekrano režimo 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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Viršutinis ekranas 50 %"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Viršutinis ekranas 30 %"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Apatinis ekranas viso ekrano režimu"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Išskaidyti kairėn"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Išskaidyti dešinėn"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Išskaidyti viršuje"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Išskaidyti apačioje"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Vienos rankos režimo naudojimas"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Jei norite išeiti, perbraukite aukštyn nuo ekrano apačios arba palieskite bet kur virš programos"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pradėti vienos rankos režimą"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Supratau"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nėra naujausių burbulų"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Naujausi ir atsisakyti burbulai bus rodomi čia"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bet kada valdyti burbulus"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Palietę čia valdykite, kurie pokalbiai ir programos gali būti rodomi burbuluose"</string> <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="6712141648865547958">"Palieskite, kad iš naujo paleistumėte šią programą ir matytumėte aiškiau."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Palieskite, kad iš naujo paleistumėte šią programą ir matytumėte aiškiau"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Pakeiskite šios programos kraštinių santykį"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Keisti kraštinių santykį"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atšaukti"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Paleisti iš naujo"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Daugiau neberodyti"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dukart palieskite, kad\nperkeltumėte šią programą"</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="app_icon_text" msgid="2823268023931811747">"Programos piktograma"</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> + <string name="select_text" msgid="5139083974039906583">"Pasirinkti"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Ekrano kopija"</string> + <string name="close_text" msgid="4986518933445178928">"Uždaryti"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Atidaryti meniu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index e612ddda69eb..5fab5778d3d5 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Mainīt lielumu"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Paslēpt"</string> <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_forced_resizable" msgid="7429086980048964687">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Ekrāna sadalītājs"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Augšdaļa 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Augšdaļa 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Apakšdaļu pa visu ekrānu"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Sadalījums pa kreisi"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Sadalījums pa labi"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Sadalījums augšdaļā"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Sadalījums apakšdaļā"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Vienas rokas režīma izmantošana"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Lai izietu, velciet augšup no ekrāna apakšdaļas vai pieskarieties jebkurā vietā virs lietotnes"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pāriet vienas rokas režīmā"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Labi"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nav nesen aizvērtu burbuļu"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Šeit būs redzami nesen rādītie burbuļi un aizvērtie burbuļi"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Pārvaldīt burbuļus jebkurā laikā"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Pieskarieties šeit, lai pārvaldītu, kuras lietotnes un sarunas var rādīt burbulī"</string> <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="6712141648865547958">"Pieskarieties, lai restartētu šo lietotni un uzlabotu attēlojumu."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Pieskarieties, lai restartētu šo lietotni un uzlabotu attēlojumu."</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Iestatījumos mainiet šīs lietotnes malu attiecību."</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mainīt malu attiecību"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atcelt"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartēt"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vairs nerādīt"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Veiciet dubultskārienu,\nlai pārvietotu šo lietotni"</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="app_icon_text" msgid="2823268023931811747">"Lietotnes ikona"</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> + <string name="select_text" msgid="5139083974039906583">"Atlasīt"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Ekrānuzņēmums"</string> + <string name="close_text" msgid="4986518933445178928">"Aizvērt"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Atvērt izvēlni"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index 3c5449c22690..906fc094ea5a 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Апликацијата можеби нема да работи со поделен екран"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апликацијата не поддржува поделен екран"</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_divider" msgid="6407584574218956849">"Разделник на поделен екран"</string> + <string name="divider_title" msgid="1963391955593749442">"Разделник на поделен екран"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горниот 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Горниот 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Долниот на цел екран"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Подели налево"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Подели надесно"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Подели нагоре"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Подели долу"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Користење на режимот со една рака"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"За да излезете, повлечете нагоре од дното на екранот или допрете каде било над апликацијата"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Започни го режимот со една рака"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Контролирајте ги балончињата во секое време"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Допрете тука за да одредите на кои апл. и разговори може да се појават балончиња"</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="6712141648865547958">"Допрете за да ја рестартирате апликацијава за подобар приказ."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Допрете за да ја рестартирате апликацијава за подобар приказ"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Промени го соодносот на апликацијава во „Поставки“"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Променување на соодносот"</string> <string name="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="7739895354143295358">"Погледнете и направете повеќе"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Повлечете во друга апликација за поделен екран"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Повлечете друга апликација за поделен екран"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартирај"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не прикажувај повторно"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Допрете двапати за да ја\nпоместите апликацијава"</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="app_icon_text" msgid="2823268023931811747">"Икона на апликацијата"</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> + <string name="select_text" msgid="5139083974039906583">"Изберете"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Слика од екранот"</string> + <string name="close_text" msgid="4986518933445178928">"Затворете"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Отвори го менито"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index 0df5c3a84a42..65e6d2c4c8e7 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"സ്ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"സ്ക്രീൻ വിഭജന മോഡിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല"</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_divider" msgid="6407584574218956849">"സ്ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string> + <string name="divider_title" msgid="1963391955593749442">"സ്ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"മുകളിൽ 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"മുകളിൽ 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"താഴെ പൂർണ്ണ സ്ക്രീൻ"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"ഇടത് ഭാഗത്തേക്ക് വിഭജിക്കുക"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"വലത് ഭാഗത്തേക്ക് വിഭജിക്കുക"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"മുകളിലേക്ക് വിഭജിക്കുക"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"താഴേക്ക് വിഭജിക്കുക"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ഒറ്റക്കൈ മോഡ് എങ്ങനെ ഉപയോഗിക്കാം"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"പുറത്ത് കടക്കാൻ, സ്ക്രീനിന്റെ ചുവടെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക അല്ലെങ്കിൽ ആപ്പിന് മുകളിലായി എവിടെയെങ്കിലും ടാപ്പ് ചെയ്യുക"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ഒറ്റക്കൈ മോഡ് ആരംഭിച്ചു"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ബബിളുകൾ ഏതുസമയത്തും നിയന്ത്രിക്കുക"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ഏതൊക്കെ ആപ്പുകളും സംഭാഷണങ്ങളും ബബിൾ ചെയ്യാനാകുമെന്നത് മാനേജ് ചെയ്യാൻ ഇവിടെ ടാപ്പ് ചെയ്യുക"</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="6712141648865547958">"മികച്ച കാഴ്ചയ്ക്കായി ഈ ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക."</string> + <string name="restart_button_description" msgid="4564728020654658478">"മികച്ച കാഴ്ചയ്ക്കായി ഈ ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ഈ ആപ്പിന്റെ വീക്ഷണ അനുപാതം, ക്രമീകരണത്തിൽ മാറ്റുക"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"വീക്ഷണ അനുപാതം മാറ്റുക"</string> <string name="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="7739895354143295358">"കൂടുതൽ കാണുക, ചെയ്യുക"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"സ്ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"സ്ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"റദ്ദാക്കുക"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"റീസ്റ്റാർട്ട് ചെയ്യൂ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"വീണ്ടും കാണിക്കരുത്"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ഈ ആപ്പ് നീക്കാൻ\nഡബിൾ ടാപ്പ് ചെയ്യുക"</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="app_icon_text" msgid="2823268023931811747">"ആപ്പ് ഐക്കൺ"</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> + <string name="select_text" msgid="5139083974039906583">"തിരഞ്ഞെടുക്കുക"</string> + <string name="screenshot_text" msgid="1477704010087786671">"സ്ക്രീൻഷോട്ട്"</string> + <string name="close_text" msgid="4986518933445178928">"അടയ്ക്കുക"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"മെനു തുറക്കുക"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index 5f9d3db43e41..44c594691e36 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Апп дэлгэцийг хуваах горимтой ажиллахгүй байж магадгүй"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апп дэлгэцийг хуваах горимыг дэмждэггүй"</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_divider" msgid="6407584574218956849">"Дэлгэцийг хуваах хуваагч"</string> + <string name="divider_title" msgid="1963391955593749442">"Дэлгэцийг хуваах хуваагч"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Дээд 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Дээд 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Доод бүтэн дэлгэц"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Зүүн талд хуваах"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Баруун талд хуваах"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Дээд талд хуваах"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Доод талд хуваах"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Нэг гарын горимыг ашиглаж байна"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл апп дээр хүссэн газраа товшино уу"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Нэг гарын горимыг эхлүүлэх"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Бөмбөлгүүдийг хүссэн үедээ хянах"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ямар апп болон харилцан ярианууд бөмбөлгөөр харагдахыг энд удирдана уу"</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="6712141648865547958">"Харагдах байдлыг сайжруулахын тулд энэ аппыг товшиж, дахин эхлүүлнэ үү."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Харагдах байдлыг сайжруулахын тулд энэ аппыг товшиж, дахин эхлүүлнэ үү"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Энэ аппын харьцааг Тохиргоонд өөрчилнө үү"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Харьцааг өөрчлөх"</string> <string name="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="7739895354143295358">"Харж илүү ихийг хий"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Дэлгэцийг хуваахын тулд өөр апп руу чирнэ үү"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Дэлгэц хуваах горимд ашиглахын тулд өөр аппыг чирнэ үү"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Цуцлах"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Дахин эхлүүлэх"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Дахиж бүү харуул"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Энэ аппыг зөөхийн тулд\nхоёр товшино уу"</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="app_icon_text" msgid="2823268023931811747">"Aппын дүрс тэмдэг"</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> + <string name="select_text" msgid="5139083974039906583">"Сонгох"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Дэлгэцийн агшин"</string> + <string name="close_text" msgid="4986518933445178928">"Хаах"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Цэс нээх"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index 4e29c11b2299..bd898c4395ee 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"अॅप कदाचित स्प्लिट स्क्रीनसह काम करणार नाही"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"अॅप हे स्प्लिट स्क्रीनला सपोर्ट करत नाही"</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_divider" msgid="6407584574218956849">"स्प्लिट स्क्रीन विभाजक"</string> + <string name="divider_title" msgid="1963391955593749442">"स्प्लिट स्क्रीन विभाजक"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"शीर्ष 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"शीर्ष 10"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"तळाशी फुल स्क्रीन"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"डावीकडे स्प्लिट करा"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"उजवीकडे स्प्लिट करा"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"सर्वात वरती स्प्लिट करा"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"खालती स्प्लिट करा"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"एकहाती मोड वापरणे"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"बाहेर पडण्यासाठी स्क्रीनच्या खालून वरच्या दिशेने स्वाइप करा किंवा ॲपवर कोठेही टॅप करा"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"एकहाती मोड सुरू करा"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"बबल कधीही नियंत्रित करा"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"कोणती ॲप्स आणि संभाषणे बबल होऊ शकतात हे व्यवस्थापित करण्यासाठी येथे टॅप करा"</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="6712141648865547958">"अधिक चांगल्या व्ह्यूसाठी हे अॅप रीस्टार्ट करण्याकरिता टॅप करा."</string> + <string name="restart_button_description" msgid="4564728020654658478">"अधिक चांगल्या दृश्यासाठी हे अॅप रीस्टार्ट करण्याकरिता टॅप करा"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"सेटिंग्ज मध्ये या ॲपचा आस्पेक्ट रेशो बदला"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"आस्पेक्ट रेशो बदला"</string> <string name="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="7739895354143295358">"पहा आणि आणखी बरेच काही करा"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट-स्क्रीन वापरण्यासाठी दुसऱ्या ॲपमध्ये ड्रॅग करा"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रीन वापरण्यासाठी दुसरे ॲप ड्रॅग करा"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द करा"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करा"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"पुन्हा दाखवू नका"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"हे ॲप हलवण्यासाठी\nदोनदा टॅप करा"</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="app_icon_text" msgid="2823268023931811747">"अॅप आयकन"</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> + <string name="select_text" msgid="5139083974039906583">"निवडा"</string> + <string name="screenshot_text" msgid="1477704010087786671">"स्क्रीनशॉट"</string> + <string name="close_text" msgid="4986518933445178928">"बंद करा"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"मेनू उघडा"</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 8a8977979217..89654d0a5750 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml @@ -24,7 +24,7 @@ <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="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> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index 27844728b8aa..86a7025f5e39 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ubah saiz"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Sembunyikan"</string> <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_forced_resizable" msgid="7429086980048964687">"Apl mungkin tidak berfungsi dengan skrin pisah"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Pembahagi skrin pisah"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Atas 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Skrin penuh bawah"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Pisah kiri"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Pisah kanan"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Pisah atas"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Pisah bawah"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Menggunakan mod sebelah tangan"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Untuk keluar, leret ke atas daripada bahagian bawah skrin atau ketik pada mana-mana di bahagian atas apl"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Mulakan mod sebelah tangan"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Tiada gelembung terbaharu"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Gelembung baharu dan gelembung yang diketepikan akan dipaparkan di sini"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kawal gelembung pada bila-bila masa"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ketik di sini untuk mengurus apl dan perbualan yang boleh menggunakan gelembung"</string> <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="6712141648865547958">"Ketik untuk memulakan semula apl ini untuk mendapatkan paparan yang lebih baik."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Ketik untuk memulakan semula apl ini untuk mendapatkan paparan yang lebih baik"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Tukar nisbah bidang apl ini dalam Tetapan"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tukar nisbah bidang"</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="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_split_screen_text" msgid="449233070804658627">"Seret masuk apl lain untuk menggunakan 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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulakan semula"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tunjukkan lagi"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ketik dua kali untuk\nalih apl ini"</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="app_icon_text" msgid="2823268023931811747">"Ikon Apl"</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> + <string name="select_text" msgid="5139083974039906583">"Pilih"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Tangkapan skrin"</string> + <string name="close_text" msgid="4986518933445178928">"Tutup"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Buka Menu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index 2b781069695f..4c494eb586a2 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"အက်ပ်တွင် မျက်နှာပြင် ခွဲ၍ပြသခြင်းကို မပံ့ပိုးပါ"</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_divider" msgid="6407584574218956849">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ပိုင်းခြားစနစ်"</string> + <string name="divider_title" msgid="1963391955593749442">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ပိုင်းခြားစနစ်"</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> @@ -49,14 +49,10 @@ <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> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"ဘယ်ဘက်ကို ခွဲရန်"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"ညာဘက်ကို ခွဲရန်"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"ထိပ်ပိုင်းကို ခွဲရန်"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"အောက်ခြေကို ခွဲရန်"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"လက်တစ်ဖက်သုံးမုဒ် အသုံးပြုခြင်း"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ထွက်ရန် ဖန်သားပြင်၏အောက်ခြေမှ အပေါ်သို့ပွတ်ဆွဲပါ သို့မဟုတ် အက်ပ်အပေါ်ဘက် မည်သည့်နေရာတွင်မဆို တို့ပါ"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"လက်တစ်ဖက်သုံးမုဒ်ကို စတင်လိုက်သည်"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ပူဖောင်းကွက်ကို အချိန်မရွေး ထိန်းချုပ်ရန်"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ပူဖောင်းကွက်သုံးနိုင်သည့် အက်ပ်နှင့် စကားဝိုင်းများ စီမံရန် ဤနေရာကို တို့ပါ"</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="6712141648865547958">"ပိုကောင်းသောမြင်ကွင်းအတွက် ဤအက်ပ်ပြန်စရန် တို့နိုင်သည်။"</string> + <string name="restart_button_description" msgid="4564728020654658478">"ပိုကောင်းသောမြင်ကွင်းအတွက် ဤအက်ပ်ပြန်စရန် တို့ပါ"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ဆက်တင်များတွင် ဤအက်ပ်၏အချိုးအစားကို ပြောင်းရန်"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"အချိုးစား ပြောင်းရန်"</string> <string name="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="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"မျက်နှာပြင် ခွဲ၍ပြသနိုင်ရန် နောက်အက်ပ်တစ်ခုကို ဖိဆွဲပါ"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းအတွက် အက်ပ်နောက်တစ်ခုကို ဖိဆွဲပါ"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"မလုပ်တော့"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"ပြန်စရန်"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"နောက်ထပ်မပြပါနှင့်"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ဤအက်ပ်ကို ရွှေ့ရန်\nနှစ်ချက်တို့ပါ"</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="app_icon_text" msgid="2823268023931811747">"အက်ပ်သင်္ကေတ"</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> + <string name="select_text" msgid="5139083974039906583">"ရွေးရန်"</string> + <string name="screenshot_text" msgid="1477704010087786671">"ဖန်သားပြင်ဓာတ်ပုံ"</string> + <string name="close_text" msgid="4986518933445178928">"ပိတ်ရန်"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"မီနူး ဖွင့်ရန်"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 3e7c63a53bfc..e9f90c0cb0ea 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Endre størrelse"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Oppbevar"</string> <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_forced_resizable" msgid="7429086980048964687">"Det kan hende at appen ikke fungerer med delt skjerm"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Skilleelement for delt skjerm"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Sett størrelsen på den øverste delen av skjermen til 50 %"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Sett størrelsen på den øverste delen av skjermen til 30 %"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Utvid den nederste delen av skjermen til hele skjermen"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Del opp til venstre"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Del opp til høyre"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Del opp øverst"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Del opp nederst"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Bruk av enhåndsmodus"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"For å avslutte, sveip opp fra bunnen av skjermen eller trykk hvor som helst over appen"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start enhåndsmodus"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Greit"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen nylige bobler"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nylige bobler og avviste bobler vises her"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontroller bobler når som helst"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Trykk her for å administrere hvilke apper og samtaler som kan vises i bobler"</string> <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="6712141648865547958">"Trykk for å starte denne appen på nytt for bedre visning."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Trykk for å starte denne appen på nytt og få en bedre visning"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Endre høyde/bredde-forholdet for denne appen i innstillingene"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Endre høyde/bredde-forholdet"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Avbryt"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Start på nytt"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ikke vis dette igjen"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dobbelttrykk for\nå flytte denne appen"</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="app_icon_text" msgid="2823268023931811747">"Appikon"</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> + <string name="select_text" msgid="5139083974039906583">"Velg"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Skjermdump"</string> + <string name="close_text" msgid="4986518933445178928">"Lukk"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Åpne menyen"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index 1865ee5b5102..dcfff7c30a1a 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"यो एपले स्प्लिट स्क्रिन मोडमा काम नगर्न सक्छ"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"यो एप स्प्लिट स्क्रिन मोडमा प्रयोग गर्न मिल्दैन"</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_divider" msgid="6407584574218956849">"स्प्लिट स्क्रिन डिभाइडर"</string> + <string name="divider_title" msgid="1963391955593749442">"स्प्लिट स्क्रिन डिभाइडर"</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> @@ -49,14 +49,10 @@ <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> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"बायाँतिर स्प्लिट गर्नुहोस्"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"दायाँतिर स्प्लिट गर्नुहोस्"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"सिरानतिर स्प्लिट गर्नुहोस्"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"पुछारतिर स्प्लिट गर्नुहोस्"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"एक हाते मोड प्रयोग गरिँदै छ"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"बाहिर निस्कन, स्क्रिनको पुछारबाट माथितिर स्वाइप गर्नुहोस् वा एपभन्दा माथि जुनसुकै ठाउँमा ट्याप गर्नुहोस्"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"एक हाते मोड सुरु गर्नुहोस्"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जुनसुकै बेला बबलसम्बन्धी सुविधा नियन्त्रण गर्नुहोस्"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"कुन एप र कुराकानी बबल प्रयोग गर्न सक्छन् भन्ने कुराको व्यवस्थापन गर्न यहाँ ट्याप गर्नुहोस्"</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="6712141648865547958">"यो एप अझ राम्रो हेर्न मिल्ने बनाउनका लागि यसलाई रिस्टार्ट गर्न ट्याप गर्नुहोस्।"</string> + <string name="restart_button_description" msgid="4564728020654658478">"अझ राम्रो भ्यू प्राप्त गर्नका लागि यो एप रिस्टार्ट गर्न ट्याप गर्नुहोस्"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"सेटिङमा गई यो एपको एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्"</string> <string name="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="7739895354143295358">"थप कुरा हेर्नुहोस् र गर्नुहोस्"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द गर्नुहोस्"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"रिस्टार्ट गर्नुहोस्"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फेरि नदेखाइयोस्"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"यो एप सार्न डबल\nट्याप गर्नुहोस्"</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="app_icon_text" msgid="2823268023931811747">"एपको आइकन"</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> + <string name="select_text" msgid="5139083974039906583">"चयन गर्नुहोस्"</string> + <string name="screenshot_text" msgid="1477704010087786671">"स्क्रिनसट"</string> + <string name="close_text" msgid="4986518933445178928">"बन्द गर्नुहोस्"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"मेनु खोल्नुहोस्"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 91437ac2302f..2f560f04205c 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Formaat aanpassen"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Verbergen"</string> <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_forced_resizable" msgid="7429086980048964687">"De app werkt misschien niet met gesplitst scherm"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App ondersteunt geen 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_divider" msgid="6407584574218956849">"Scheiding voor gesplitst scherm"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bovenste scherm 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Bovenste scherm 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Onderste scherm op volledig scherm"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Gesplitst scherm links"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Gesplitst scherm rechts"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Gesplitst scherm boven"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Gesplitst scherm onder"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Bediening met 1 hand gebruiken"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Als je wilt afsluiten, swipe je omhoog vanaf de onderkant van het scherm of tik je ergens boven de app"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Bediening met 1 hand starten"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Geen recente bubbels"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recente bubbels en gesloten bubbels zie je hier"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubbels beheren wanneer je wilt"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tik hier om te beheren welke apps en gesprekken als bubbel kunnen worden getoond"</string> <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="6712141648865547958">"Tik om deze app opnieuw op te starten voor een betere weergave."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tik om deze app opnieuw op te starten voor een betere weergave"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Wijzig de beeldverhouding van deze app in Instellingen"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Beeldverhouding wijzigen"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuleren"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Opnieuw opstarten"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Niet opnieuw tonen"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dubbeltik om\ndeze app te verplaatsen"</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="app_icon_text" msgid="2823268023931811747">"App-icoon"</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> + <string name="select_text" msgid="5139083974039906583">"Selecteren"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Sluiten"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Menu openen"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index d330749c82d3..ad25de51a3e7 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -26,19 +26,19 @@ <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_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_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_forced_resizable" msgid="7429086980048964687">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନରେ ଆପ କାମ କରିନପାରେ"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନକୁ ଆପ ସମର୍ଥନ କରେ ନାହିଁ"</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_divider" msgid="6407584574218956849">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଡିଭାଇଡର"</string> + <string name="divider_title" msgid="1963391955593749442">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଡିଭାଇଡର"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ଉପର ଆଡ଼କୁ 50% କରନ୍ତୁ"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ଉପର ଆଡ଼କୁ 30% କରନ୍ତୁ"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ତଳ ଅଂଶର ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"ବାମପଟକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"ଡାହାଣପଟକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"ଶୀର୍ଷକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"ନିମ୍ନକୁ ସ୍ଲିଟ କରନ୍ତୁ"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ଏକ-ହାତ ମୋଡ୍ ବ୍ୟବହାର କରି"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ବାହାରି ଯିବା ପାଇଁ, ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ କିମ୍ବା ଆପରେ ଯେ କୌଣସି ସ୍ଥାନରେ ଟାପ୍ କରନ୍ତୁ"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ଏକ-ହାତ ମୋଡ୍ ଆରମ୍ଭ କରନ୍ତୁ"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ଯେ କୌଣସି ସମୟରେ ବବଲଗୁଡ଼ିକ ନିୟନ୍ତ୍ରଣ କରନ୍ତୁ"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"କେଉଁ ଆପ୍ସ ଓ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ବବଲ ହୋଇପାରିବ ତାହା ପରିଚାଳନା କରିବାକୁ ଏଠାରେ ଟାପ କରନ୍ତୁ"</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="6712141648865547958">"ଏକ ଆହୁରି ଭଲ ଭ୍ୟୁ ପାଇଁ ଏହି ଆପ ରିଷ୍ଟାର୍ଟ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string> + <string name="restart_button_description" msgid="4564728020654658478">"ଏକ ଆହୁରି ଭଲ ଭ୍ୟୁ ପାଇଁ ଏହି ଆପ ରିଷ୍ଟାର୍ଟ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ସେଟିଂସରେ ଏହି ଆପର ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string> <string name="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="7739895354143295358">"ଦେଖନ୍ତୁ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରନ୍ତୁ"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ବାତିଲ କରନ୍ତୁ"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"ରିଷ୍ଟାର୍ଟ କରନ୍ତୁ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ଏହି ଆପକୁ ମୁଭ\nକରିବା ପାଇଁ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</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="app_icon_text" msgid="2823268023931811747">"ଆପ ଆଇକନ"</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> + <string name="select_text" msgid="5139083974039906583">"ଚୟନ କରନ୍ତୁ"</string> + <string name="screenshot_text" msgid="1477704010087786671">"ସ୍କ୍ରିନସଟ"</string> + <string name="close_text" msgid="4986518933445178928">"ବନ୍ଦ କରନ୍ତୁ"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"ମେନୁ ଖୋଲନ୍ତୁ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index 51d491b50c40..4bd9d6b9d214 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ਐਪ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</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_divider" msgid="6407584574218956849">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string> + <string name="divider_title" msgid="1963391955593749442">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ਉੱਪਰ 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ਉੱਪਰ 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ਹੇਠਾਂ ਪੂਰੀ ਸਕ੍ਰੀਨ"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"ਖੱਬੇ ਪਾਸੇ ਵੰਡੋ"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"ਸੱਜੇ ਪਾਸੇ ਵੰਡੋ"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"ਸਿਖਰ \'ਤੇ ਵੰਡੋ"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"ਹੇਠਾਂ ਵੰਡੋ"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ਇੱਕ ਹੱਥ ਮੋਡ ਵਰਤਣਾ"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ਬਾਹਰ ਜਾਣ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ ਜਾਂ ਐਪ \'ਤੇ ਕਿਤੇ ਵੀ ਟੈਪ ਕਰੋ"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ਇੱਕ ਹੱਥ ਮੋਡ ਸ਼ੁਰੂ ਕਰੋ"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ਬਬਲ ਦੀ ਸੁਵਿਧਾ ਨੂੰ ਕਿਸੇ ਵੀ ਵੇਲੇ ਕੰਟਰੋਲ ਕਰੋ"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ਇਹ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਇੱਥੇ ਟੈਪ ਕਰੋ ਕਿ ਕਿਹੜੀਆਂ ਐਪਾਂ ਅਤੇ ਗੱਲਾਂਬਾਤਾਂ ਬਬਲ ਹੋ ਸਕਦੀਆਂ ਹਨ"</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="6712141648865547958">"ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਲਈ ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string> + <string name="restart_button_description" msgid="4564728020654658478">"ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਵਾਸਤੇ ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਇਸ ਐਪ ਦੇ ਆਕਾਰ ਅਨੁਪਾਤ ਨੂੰ ਬਦਲੋ"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ਆਕਾਰ ਅਨੁਪਾਤ ਬਦਲੋ"</string> <string name="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="7739895354143295358">"ਦੇਖੋ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਕਰੋ"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ਰੱਦ ਕਰੋ"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"ਮੁੜ-ਸ਼ੁਰੂ ਕਰੋ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ਇਸ ਐਪ ਦਾ ਟਿਕਾਣਾ ਬਦਲਣ ਲਈ\nਡਬਲ ਟੈਪ ਕਰੋ"</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="app_icon_text" msgid="2823268023931811747">"ਐਪ ਪ੍ਰਤੀਕ"</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> + <string name="select_text" msgid="5139083974039906583">"ਚੁਣੋ"</string> + <string name="screenshot_text" msgid="1477704010087786671">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string> + <string name="close_text" msgid="4986518933445178928">"ਬੰਦ ਕਰੋ"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index 32e9840ac791..d98be758c97e 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Zmień rozmiar"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Przenieś do schowka"</string> <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_forced_resizable" msgid="7429086980048964687">"Aplikacja może nie działać przy podzielonym ekranie"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacja nie obsługuje podzielonego 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_divider" msgid="6407584574218956849">"Linia dzielenia ekranu"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% górnej części ekranu"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% górnej części ekranu"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolna część ekranu na pełnym ekranie"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Podziel po lewej"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Podziel po prawej"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Podziel u góry"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podziel u dołu"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korzystanie z trybu jednej ręki"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Aby zamknąć, przesuń palcem z dołu ekranu w górę lub kliknij dowolne miejsce nad aplikacją"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Uruchom tryb jednej ręki"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Brak ostatnich dymków"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Tutaj będą pojawiać się ostatnie i odrzucone dymki"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Zarządzaj dymkami, kiedy chcesz"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Kliknij tutaj, aby zarządzać wyświetlaniem aplikacji i rozmów jako dymków"</string> <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="6712141648865547958">"Kliknij, aby zrestartować aplikację i zyskać lepszą widoczność."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Kliknij w celu zrestartowania aplikacji, aby lepiej się wyświetlała."</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Zmień proporcje obrazu aplikacji w Ustawieniach"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Zmień proporcje obrazu"</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="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_split_screen_text" msgid="449233070804658627">"Aby podzielić ekran, przeciągnij drugą aplikację"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anuluj"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Uruchom ponownie"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nie pokazuj ponownie"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Aby przenieść aplikację,\nkliknij dwukrotnie"</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="app_icon_text" msgid="2823268023931811747">"Ikona aplikacji"</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> + <string name="select_text" msgid="5139083974039906583">"Wybierz"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Zrzut ekranu"</string> + <string name="close_text" msgid="4986518933445178928">"Zamknij"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Otwórz menu"</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 1a29af24587a..81d325a7ec58 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionar"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ocultar"</string> <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_forced_resizable" msgid="7429086980048964687">"É possível que o app não funcione com a tela dividida"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"O app não oferece suporte à 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_divider" msgid="6407584574218956849">"Divisor de tela"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Parte superior a 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Parte inferior em tela cheia"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir para a esquerda"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir para a direita"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir para cima"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir para baixo"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ok"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nenhum balão recente"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Os balões recentes e dispensados aparecerão aqui"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controle os balões a qualquer momento"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toque aqui para gerenciar quais apps e conversas podem aparecer em balões"</string> <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="6712141648865547958">"Toque para reiniciar o app e atualizar a visualização."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar o app e atualizar a visualização"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Mude a proporção deste app nas Configurações"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mudar a proporçã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="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_split_screen_text" msgid="449233070804658627">"Arraste outro app para dividir a tela"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes para\nmover o app"</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="app_icon_text" msgid="2823268023931811747">"Ícone do app"</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> + <string name="select_text" msgid="5139083974039906583">"Selecionar"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Captura de tela"</string> + <string name="close_text" msgid="4986518933445178928">"Fechar"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Abrir o menu"</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 b2d8e0809b99..7fa592afbbe3 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -24,7 +24,7 @@ <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_notification_message" msgid="8854051911700302620">"Se não quer 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> <string name="pip_pause" msgid="690688849510295232">"Pausar"</string> <string name="pip_skip_to_next" msgid="8403429188794867653">"Mudar para o seguinte"</string> @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionar"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Armazenar"</string> <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_forced_resizable" msgid="7429086980048964687">"A app pode não funcionar com o ecrã dividido"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Divisor do ecrã dividido"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% no ecrã superior"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% no ecrã superior"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ecrã inferior inteiro"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Divisão à esquerda"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Divisão à direita"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Divisão na parte superior"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Divisão na parte inferior"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utilize o modo para uma mão"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize rapidamente para cima a partir da parte inferior do ecrã ou toque em qualquer ponto acima da app."</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nenhum balão recente"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Os balões recentes e ignorados vão aparecer aqui."</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controle os balões em qualquer altura"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toque aqui para gerir que apps e conversas podem aparecer em balões"</string> <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="6712141648865547958">"Toque para reiniciar esta app e ficar com uma melhor visão."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar esta app e ficar com uma melhor visão"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Altere o formato desta app nas Definições"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Altere o formato"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar de novo"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes\npara mover esta app"</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="app_icon_text" msgid="2823268023931811747">"Ícone da app"</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> + <string name="select_text" msgid="5139083974039906583">"Selecionar"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Captura de ecrã"</string> + <string name="close_text" msgid="4986518933445178928">"Fechar"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Abrir menu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 1a29af24587a..81d325a7ec58 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionar"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ocultar"</string> <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_forced_resizable" msgid="7429086980048964687">"É possível que o app não funcione com a tela dividida"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"O app não oferece suporte à 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_divider" msgid="6407584574218956849">"Divisor de tela"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Parte superior a 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Parte inferior em tela cheia"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir para a esquerda"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir para a direita"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir para cima"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir para baixo"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ok"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nenhum balão recente"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Os balões recentes e dispensados aparecerão aqui"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controle os balões a qualquer momento"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toque aqui para gerenciar quais apps e conversas podem aparecer em balões"</string> <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="6712141648865547958">"Toque para reiniciar o app e atualizar a visualização."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar o app e atualizar a visualização"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Mude a proporção deste app nas Configurações"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mudar a proporçã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="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_split_screen_text" msgid="449233070804658627">"Arraste outro app para dividir a tela"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes para\nmover o app"</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="app_icon_text" msgid="2823268023931811747">"Ícone do app"</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> + <string name="select_text" msgid="5139083974039906583">"Selecionar"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Captura de tela"</string> + <string name="close_text" msgid="4986518933445178928">"Fechar"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Abrir o menu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index 8f609791f20b..0341667be3e3 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"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="2733543750291266047">"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_divider" msgid="6407584574218956849">"Separator pentru ecranul împărțit"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Partea de sus: 50%"</string> <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> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Împarte în stânga"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Împarte în dreapta"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Împarte în sus"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Împarte în jos"</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, 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> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controlează baloanele oricând"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Atinge aici pentru a gestiona aplicațiile și conversațiile care pot apărea în balon"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</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="6712141648865547958">"Atinge ca să repornești aplicația pentru o vizualizare mai bună."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Atinge ca să repornești aplicația pentru o vizualizare mai bună"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Schimbă raportul de dimensiuni al aplicației din Setări"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Schimbă raportul de dimensiuni"</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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulează"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Repornește"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nu mai afișa"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Atinge de două ori\nca să muți aplicația"</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="app_icon_text" msgid="2823268023931811747">"Pictograma aplicației"</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> + <string name="select_text" msgid="5139083974039906583">"Selectează"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Captură de ecran"</string> + <string name="close_text" msgid="4986518933445178928">"Închide"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Deschide meniul"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index 0c1e4a9ad351..da234c71a009 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Когда включено разделение экрана, приложение может работать нестабильно."</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Приложение не поддерживает разделение экрана."</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_divider" msgid="6407584574218956849">"Разделитель экрана"</string> + <string name="divider_title" msgid="1963391955593749442">"Разделитель экрана"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхний на 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Верхний на 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Нижний во весь экран"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Приложение слева"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Приложение справа"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Приложение сверху"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Приложение снизу"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Использование режима управления одной рукой"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чтобы выйти, проведите по экрану снизу вверх или коснитесь области за пределами приложения."</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Запустить режим управления одной рукой"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Всплывающие чаты"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Укажите приложения и разговоры, для которых разрешены всплывающие чаты."</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="6712141648865547958">"Нажмите, чтобы перезапустить приложение и настроить удобный для просмотра вид"</string> + <string name="restart_button_description" msgid="4564728020654658478">"Нажмите, чтобы перезапустить приложение и оптимизировать размер"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Изменить соотношение сторон приложения в настройках"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Изменить соотношение сторон"</string> <string name="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="7739895354143295358">"Выполняйте несколько задач одновременно"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Отмена"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустить"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больше не показывать"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Дважды нажмите, чтобы\nпереместить приложение."</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="app_icon_text" msgid="2823268023931811747">"Значок приложения"</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> + <string name="select_text" msgid="5139083974039906583">"Выбрать"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string> + <string name="close_text" msgid="4986518933445178928">"Закрыть"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Открыть меню"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index 13ac51823937..236da5d67fc9 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"යෙදුම බෙදීම් තිරය සමග ක්රියා නොකළ හැක"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"යෙදුම බෙදුම් තිරයට සහාය නොදක්වයි"</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_divider" msgid="6407584574218956849">"බෙදුම් තිර වෙන්කරණය"</string> + <string name="divider_title" msgid="1963391955593749442">"බෙදුම් තිර වෙන්කරණය"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ඉහළම 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ඉහළම 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"පහළ පූර්ණ තිරය"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"වම බෙදන්න"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"දකුණ බෙදන්න"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"ඉහළ බෙදන්න"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"පහළ බෙදන්න"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"තනි-අත් ප්රකාරය භාවිත කරමින්"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"පිටවීමට, තිරයේ පහළ සිට ඉහළට ස්වයිප් කරන්න හෝ යෙදුමට ඉහළින් ඕනෑම තැනක තට්ටු කරන්න"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"තනි අත් ප්රකාරය ආරම්භ කරන්න"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ඕනෑම වේලාවක බුබුලු පාලනය කරන්න"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"බුබුලු කළ හැකි යෙදුම් සහ සංවාද කළමනාකරණය කිරීමට මෙහි තට්ටු කරන්න"</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="6712141648865547958">"වඩා හොඳ දසුනක් ලබා ගැනීම සඳහා මෙම යෙදුම යළි ඇරඹීමට තට්ටු කරන්න."</string> + <string name="restart_button_description" msgid="4564728020654658478">"වඩා හොඳ දසුනක් සඳහා මෙම යෙදුම යළි ඇරඹීමට තට්ටු කරන්න"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"සැකසීම් තුළ මෙම යෙදුමේ දර්ශන අනුපාතය වෙනස් කරන්න"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"දර්ශන අනුපාතය වෙනස් කරන්න"</string> <string name="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="7739895354143295358">"බලන්න සහ තවත් දේ කරන්න"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"අවලංගු කරන්න"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"යළි අරඹන්න"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"නැවත නොපෙන්වන්න"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"මෙම යෙදුම ගෙන යාමට\nදෙවරක් තට්ටු කරන්න"</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="app_icon_text" msgid="2823268023931811747">"යෙදුම් නිරූපකය"</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> + <string name="select_text" msgid="5139083974039906583">"තෝරන්න"</string> + <string name="screenshot_text" msgid="1477704010087786671">"තිර රුව"</string> + <string name="close_text" msgid="4986518933445178928">"වසන්න"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"මෙනුව විවෘත කරන්න"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index c91856c7e383..eaabdabb0285 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Zmeniť veľkosť"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Skryť"</string> <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_forced_resizable" msgid="7429086980048964687">"Aplikácia nemusí fungovať s rozdelenou obrazovkou"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Rozdeľovač obrazovky"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Horná – 50 %"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Horná – 30 %"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolná – na celú obrazovku"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Rozdeliť vľavo"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Rozdeliť vpravo"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Rozdeliť hore"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Rozdeliť dole"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Používanie režimu jednej ruky"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ukončíte potiahnutím z dolnej časti obrazovky nahor alebo klepnutím kdekoľvek nad aplikáciu"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Spustiť režim jednej ruky"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Dobre"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Žiadne nedávne bubliny"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Tu sa budú zobrazovať nedávne a zavreté bubliny"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Ovládajte bubliny kedykoľvek"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Klepnite tu a spravujte, ktoré aplikácie a konverzácie môžu ovládať bubliny"</string> <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="6712141648865547958">"Ak chcete zlepšiť zobrazenie, klepnutím túto aplikáciu reštartujte."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Ak chcete zlepšiť zobrazenie, klepnutím túto aplikáciu reštartujte"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Zmeniť pomer strán tejto aplikácie v Nastaveniach"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Zmeniť pomer strán"</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="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_split_screen_text" msgid="449233070804658627">"Rozdelenú obrazovku môžete použiť presunutím do inej 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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušiť"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reštartovať"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Už nezobrazovať"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Túto aplikáciu\npresuniete dvojitým klepnutím"</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="app_icon_text" msgid="2823268023931811747">"Ikona aplikácie"</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> + <string name="select_text" msgid="5139083974039906583">"Vybrať"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Snímka obrazovky"</string> + <string name="close_text" msgid="4986518933445178928">"Zavrieť"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Otvoriť ponuku"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index 744492ca2ea2..514a0b3548db 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Spremeni velikost"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Zakrij"</string> <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_forced_resizable" msgid="7429086980048964687">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Razdelilnik zaslonov"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Zgornji 50 %"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Zgornji 30 %"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Spodnji v celozaslonski način"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Delitev levo"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Delitev desno"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Delitev zgoraj"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Delitev spodaj"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Uporaba enoročnega načina"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Za izhod povlecite z dna zaslona navzgor ali se dotaknite na poljubnem mestu nad aplikacijo"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Zagon enoročnega načina"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"V redu"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ni nedavnih oblačkov"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Tukaj bodo prikazani tako nedavni kot tudi opuščeni oblački"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljanje oblačkov"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dotaknite se tukaj za upravljanje aplikacij in pogovorov, ki so lahko prikazani v oblačkih"</string> <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="6712141648865547958">"Če želite boljši prikaz, se dotaknite za vnovični zagon te aplikacije."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Če želite boljši prikaz, se dotaknite za vnovični zagon te aplikacije."</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Razmerje stranic te aplikacije spremenite v nastavitvah."</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Sprememba razmerja stranic"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Prekliči"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Znova zaženi"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikaži več"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvakrat se dotaknite\nza premik te aplikacije"</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="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</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> + <string name="select_text" msgid="5139083974039906583">"Izberi"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Posnetek zaslona"</string> + <string name="close_text" msgid="4986518933445178928">"Zapri"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Odpri meni"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index 9afbbbacb6a3..790119b8e9cc 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ndrysho përmasat"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Fshih"</string> <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_forced_resizable" msgid="7429086980048964687">"Aplikacioni mund të mos funksionojë me ekranin e ndarë"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Ndarësi i ekranit të ndarë"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Lart 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Lart 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ekrani i plotë poshtë"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Ndaj majtas"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Ndaj djathtas"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Ndaj lart"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Ndaj në fund"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Po përdor modalitetin e përdorimit me një dorë"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Për të dalë, rrëshqit lart nga fundi i ekranit ose trokit diku mbi aplikacion"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Modaliteti i përdorimit me një dorë"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"E kuptova"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nuk ka flluska të fundit"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Flluskat e fundit dhe flluskat e hequra do të shfaqen këtu"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrollo flluskat në çdo moment"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Trokit këtu për të menaxhuar aplikacionet e bisedat që do të shfaqen në flluska"</string> <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="6712141648865547958">"Trokit për të rifilluar këtë aplikacion për një pamje më të mirë."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Trokit për ta rinisur këtë aplikacion për një pamje më të mirë"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ndrysho raportin e pamjes së këtij aplikacioni te \"Cilësimet\""</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ndrysho raportin e pamjes"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulo"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Rinis"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Mos e shfaq përsëri"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Trokit dy herë për të\nlëvizur këtë aplikacion"</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="app_icon_text" msgid="2823268023931811747">"Ikona e aplikacionit"</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> + <string name="select_text" msgid="5139083974039906583">"Zgjidh"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Pamja e ekranit"</string> + <string name="close_text" msgid="4986518933445178928">"Mbyll"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Hap menynë"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index c252fd7d8962..9fd9f3ed18a9 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Апликација можда неће радити са подељеним екраном."</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апликација не подржава подељени екран."</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_divider" msgid="6407584574218956849">"Разделник подељеног екрана"</string> + <string name="divider_title" msgid="1963391955593749442">"Разделник подељеног екрана"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горњи екран 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Горњи екран 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Режим целог екрана за доњи екран"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Поделите лево"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Поделите десно"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Поделите у врху"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Поделите у дну"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Коришћење режима једном руком"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Да бисте изашли, превуците нагоре од дна екрана или додирните било где изнад апликације"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Покрените режим једном руком"</string> @@ -80,31 +76,46 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Контролишите облачиће у сваком тренутку"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Додирните овде и одредите које апликације и конверзације могу да имају облачић"</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="6712141648865547958">"Додирните да бисте рестартовали ову апликацију ради бољег приказа."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Додирните да бисте рестартовали ову апликацију ради бољег приказа"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Промените размеру ове апликације у Подешавањима"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Промени размеру"</string> <string name="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="7739895354143295358">"Видите и урадите више"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Превуците другу апликацију да бисте користили подељени екран"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Превуците другу апликацију да бисте користили подељени екран"</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="letterbox_restart_dialog_title" msgid="8543049527871033505">"Желите ли да рестартујете ради бољег приказа?"</string> - <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартујете апликацију да би изгледала боље на екрану, с тим што можете да изгубите оно што сте урадили или несачуване промене, ако их има"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартујете апликацију да би изгледала боље на екрану, али можете да изгубите напредак или несачуване промене"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартуј"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не приказуј поново"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Двапут додирните да бисте\nпреместили ову апликацију"</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="app_icon_text" msgid="2823268023931811747">"Икона апликације"</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> + <string name="select_text" msgid="5139083974039906583">"Изаберите"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Снимак екрана"</string> + <string name="close_text" msgid="4986518933445178928">"Затворите"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Отворите мени"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index 92622cb92a70..f7f218e4ded7 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ändra storlek"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Utför stash"</string> <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_forced_resizable" msgid="7429086980048964687">"Appen kanske inte fungerar med delad skärm"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Avdelare för delad skärm"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Övre 50 %"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Övre 30 %"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Helskärm på nedre skärm"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Till vänster på delad skärm"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Till höger på delad skärm"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Upptill på delad skärm"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Nedtill på delad skärm"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Använda enhandsläge"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Avsluta genom att svepa uppåt från skärmens nederkant eller trycka ovanför appen"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Starta enhandsläge"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Inga nya bubblor"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"De senaste bubblorna och ignorerade bubblor visas här"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Styr bubblor när som helst"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tryck här för att hantera vilka appar och konversationer som får visas i bubblor"</string> <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="6712141648865547958">"Tryck för att starta om appen och få en bättre vy."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Tryck för att starta om appen och få en bättre vy"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ändra appens bildformat i inställningarna"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ändra bildformat"</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="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_split_screen_text" msgid="449233070804658627">"Dra till en annan app för att dela upp skärmen"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Avbryt"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Starta om"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Visa inte igen"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tryck snabbt två gånger\nför att flytta denna app"</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="app_icon_text" msgid="2823268023931811747">"Appikon"</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> + <string name="select_text" msgid="5139083974039906583">"Välj"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Skärmbild"</string> + <string name="close_text" msgid="4986518933445178928">"Stäng"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Öppna menyn"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index 6d92040b7933..83173f3649c0 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Badilisha ukubwa"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ficha"</string> <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_forced_resizable" msgid="7429086980048964687">"Huenda programu isifanye kazi kwenye skrini iliyogawanywa"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Programu haifanyi kazi kwenye 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_divider" msgid="6407584574218956849">"Kitenganishi cha kugawa skrini"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Juu 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Juu 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Skrini nzima ya chini"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Gawanya sehemu ya kushoto"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Gawanya sehemu ya kulia"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Gawanya sehemu ya juu"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Gawanya sehemu ya chini"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Kutumia hali ya kutumia kwa mkono mmoja"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ili ufunge, telezesha kidole juu kutoka sehemu ya chini ya skrini au uguse mahali popote juu ya programu"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Anzisha hali ya kutumia kwa mkono mmoja"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Nimeelewa"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Hakuna viputo vya hivi majuzi"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Viputo vya hivi karibuni na vile vilivyoondolewa vitaonekana hapa"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Dhibiti viputo wakati wowote"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Gusa hapa ili udhibiti programu na mazungumzo yanayoweza kutumia viputo"</string> <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="6712141648865547958">"Gusa ili uzime kisha uwashe programu hii, ili upate mwonekano bora."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Gusa ili uzime kisha uwashe programu hii, ili upate mwonekano bora"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Badilisha uwiano wa programu hii katika Mipangilio"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Badilisha uwiano wa kipengele"</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="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_split_screen_text" msgid="449233070804658627">"Buruta katika programu nyingine ili utumie 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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ghairi"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Zima kisha uwashe"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Usionyeshe tena"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Gusa mara mbili ili\nusogeze programu hii"</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="app_icon_text" msgid="2823268023931811747">"Aikoni ya Programu"</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> + <string name="select_text" msgid="5139083974039906583">"Chagua"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Picha ya skrini"</string> + <string name="close_text" msgid="4986518933445178928">"Funga"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Fungua Menyu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index 8cf631b5355d..ea2ee9c22c08 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"அளவு மாற்று"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string> <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_forced_resizable" msgid="7429086980048964687">"திரைப் பிரிப்புப் பயன்முறையில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"திரைப் பிரிப்புப் பயன்முறையை ஆப்ஸ் ஆதரிக்காது"</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_divider" msgid="6407584574218956849">"திரைப் பிரிப்பான்"</string> + <string name="divider_title" msgid="1963391955593749442">"திரைப் பிரிப்பான்"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"மேலே 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"மேலே 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"கீழ்ப்புறம் முழுத் திரை"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"இடதுபுறமாகப் பிரிக்கும்"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"வலதுபுறமாகப் பிரிக்கும்"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"மேற்புறமாகப் பிரிக்கும்"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"கீழ்புறமாகப் பிரிக்கும்"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ஒற்றைக் கைப் பயன்முறையைப் பயன்படுத்துதல்"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"வெளியேற, திரையின் கீழிருந்து மேல்நோக்கி ஸ்வைப் செய்யவும் அல்லது ஆப்ஸுக்கு மேலே ஏதேனும் ஓர் இடத்தில் தட்டவும்"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ஒற்றைக் கைப் பயன்முறையைத் தொடங்கும்"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"எப்போது வேண்டுமானாலும் குமிழ்களைக் கட்டுப்படுத்துங்கள்"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"எந்தெந்த ஆப்ஸும் உரையாடல்களும் குமிழியாகலாம் என்பதை நிர்வகிக்க இங்கே தட்டுங்கள்"</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="6712141648865547958">"இங்கு தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கி, ஆப்ஸ் காட்டப்படும் விதத்தை இன்னும் சிறப்பாக்கலாம்."</string> + <string name="restart_button_description" msgid="4564728020654658478">"இங்கு தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கி, ஆப்ஸ் காட்டப்படும் விதத்தை இன்னும் சிறப்பாக்கலாம்"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"அமைப்புகளில் இந்த ஆப்ஸின் தோற்ற விகிதத்தை மாற்றும்"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"தோற்ற விகிதத்தை மாற்றும்"</string> <string name="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="7739895354143295358">"பலவற்றைப் பார்த்தல் மற்றும் செய்தல்"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ரத்துசெய்"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"மீண்டும் தொடங்கு"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"மீண்டும் காட்டாதே"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"இந்த ஆப்ஸை நகர்த்த\nஇருமுறை தட்டவும்"</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="app_icon_text" msgid="2823268023931811747">"ஆப்ஸ் ஐகான்"</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> + <string name="select_text" msgid="5139083974039906583">"தேர்ந்தெடுக்கும்"</string> + <string name="screenshot_text" msgid="1477704010087786671">"ஸ்கிரீன்ஷாட்"</string> + <string name="close_text" msgid="4986518933445178928">"மூடும்"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"மெனுவைத் திற"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index a4dcd950f321..e2772bf7311e 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"స్ప్లిట్ స్క్రీన్తో యాప్ పని చేయకపోవచ్చు"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"యాప్లో స్ప్లిట్ స్క్రీన్కు సపోర్ట్ లేదు"</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_divider" msgid="6407584574218956849">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string> + <string name="divider_title" msgid="1963391955593749442">"స్ప్లిట్ స్క్రీన్ డివైడర్"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ఎగువ 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ ఫుల్-స్క్రీన్"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"ఎడమ వైపున్న భాగంలో విభజించండి"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"కుడి వైపున్న భాగంలో విభజించండి"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"ఎగువ భాగంలో విభజించండి"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"దిగువ భాగంలో విభజించండి"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"వన్-హ్యాండెడ్ మోడ్ను ఉపయోగించడం"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"వన్-హ్యాండెడ్ మోడ్ను ప్రారంభిస్తుంది"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"బబుల్స్ను ఎప్పుడైనా కంట్రోల్ చేయండి"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ఏ యాప్లు, సంభాషణలను బబుల్ చేయాలో మేనేజ్ చేయడానికి ఇక్కడ ట్యాప్ చేయండి"</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="6712141648865547958">"మెరుగైన వీక్షణ కోసం ఈ యాప్ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేయండి."</string> + <string name="restart_button_description" msgid="4564728020654658478">"మెరుగైన వీక్షణ కోసం ఈ యాప్ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేయండి"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"సెట్టింగ్లలో ఈ యాప్ ఆకార నిష్పత్తిని మార్చండి"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ఆకార నిష్పత్తిని మార్చండి"</string> <string name="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="7739895354143295358">"చూసి, మరిన్ని చేయండి"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"స్ప్లిట్-స్క్రీన్ కోసం మరొక యాప్లోకి లాగండి"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"స్ప్లిట్ స్క్రీన్ కోసం మరొక యాప్లోకి లాగండి"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"రద్దు చేయండి"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"రీస్టార్ట్ చేయండి"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"మళ్లీ చూపవద్దు"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ఈ యాప్ను తరలించడానికి\nడబుల్-ట్యాప్ చేయండి"</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="app_icon_text" msgid="2823268023931811747">"యాప్ చిహ్నం"</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> + <string name="select_text" msgid="5139083974039906583">"ఎంచుకోండి"</string> + <string name="screenshot_text" msgid="1477704010087786671">"స్క్రీన్షాట్"</string> + <string name="close_text" msgid="4986518933445178928">"మూసివేయండి"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"మెనూను తెరవండి"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml index cc0333efd82b..da8abde8407f 100644 --- a/libs/WindowManager/Shell/res/values-television/config.xml +++ b/libs/WindowManager/Shell/res/values-television/config.xml @@ -45,11 +45,16 @@ <integer name="config_pipForceCloseDelay">5000</integer> <!-- Animation duration when exit starting window: fade out icon --> - <integer name="starting_window_app_reveal_icon_fade_out_duration">0</integer> + <integer name="starting_window_app_reveal_icon_fade_out_duration">500</integer> - <!-- Animation duration when exit starting window: reveal app --> + <!-- Animation delay when exit starting window: reveal app --> <integer name="starting_window_app_reveal_anim_delay">0</integer> <!-- Animation duration when exit starting window: reveal app --> - <integer name="starting_window_app_reveal_anim_duration">0</integer> + <integer name="starting_window_app_reveal_anim_duration">500</integer> + + <!-- Default animation type when hiding the starting window. The possible values are: + - 0 for radial vanish + slide up + - 1 for fade out --> + <integer name="starting_window_exit_animation_type">1</integer> </resources> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index 1e900d8ad81f..14bdc4bb040f 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"แอปอาจใช้ไม่ได้กับโหมดแยกหน้าจอ"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"แอปไม่รองรับการแยกหน้าจอ"</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_divider" msgid="6407584574218956849">"เส้นแยกหน้าจอ"</string> + <string name="divider_title" msgid="1963391955593749442">"เส้นแยกหน้าจอ"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ด้านบน 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ด้านบน 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"เต็มหน้าจอด้านล่าง"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"แยกไปทางซ้าย"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"แยกไปทางขวา"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"แยกไปด้านบน"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"แยกไปด้านล่าง"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"การใช้โหมดมือเดียว"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"หากต้องการออก ให้เลื่อนขึ้นจากด้านล่างของหน้าจอหรือแตะที่ใดก็ได้เหนือแอป"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"เริ่มโหมดมือเดียว"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ควบคุมบับเบิลได้ทุกเมื่อ"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"แตะที่นี่เพื่อจัดการแอปและการสนทนาที่แสดงเป็นบับเบิลได้"</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="6712141648865547958">"แตะเพื่อรีสตาร์ทแอปนี้และรับมุมมองที่ดียิ่งขึ้น"</string> + <string name="restart_button_description" msgid="4564728020654658478">"แตะเพื่อรีสตาร์ทแอปนี้และรับมุมมองที่ดียิ่งขึ้น"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"เปลี่ยนสัดส่วนภาพของแอปนี้ในการตั้งค่า"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"เปลี่ยนอัตราส่วนกว้างยาว"</string> <string name="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="7739895354143295358">"รับชมและทำสิ่งต่างๆ ได้มากขึ้น"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ยกเลิก"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"รีสตาร์ท"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ไม่ต้องแสดงข้อความนี้อีก"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"แตะสองครั้ง\nเพื่อย้ายแอปนี้"</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="app_icon_text" msgid="2823268023931811747">"ไอคอนแอป"</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> + <string name="select_text" msgid="5139083974039906583">"เลือก"</string> + <string name="screenshot_text" msgid="1477704010087786671">"ภาพหน้าจอ"</string> + <string name="close_text" msgid="4986518933445178928">"ปิด"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"เปิดเมนู"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index 8d5d0ed7b8da..208e8cbc2277 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"I-resize"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"I-stash"</string> <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_forced_resizable" msgid="7429086980048964687">"Posibleng hindi gumana sa split screen ang app"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Divider ng split screen"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gawing 50% ang nasa itaas"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gawing 30% ang nasa itaas"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"I-full screen ang nasa ibaba"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Hatiin sa kaliwa"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Hatiin sa kanan"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Hatiin sa itaas"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Hatiin sa ilalim"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Paggamit ng one-hand mode"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para lumabas, mag-swipe pataas mula sa ibaba ng screen o mag-tap kahit saan sa itaas ng app"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Simulan ang one-hand mode"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Walang kamakailang bubble"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Lalabas dito ang mga kamakailang bubble at na-dismiss na bubble"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrolin ang mga bubble anumang oras"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Mag-tap dito para pamahalaan ang mga app at conversion na puwedeng mag-bubble"</string> <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="6712141648865547958">"I-tap para i-restart ang app na ito para sa mas magandang view."</string> + <string name="restart_button_description" msgid="4564728020654658478">"I-tap para i-restart ang app na ito para sa mas magandang view"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Baguhin ang aspect ratio ng app na ito sa Mga Setting"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Baguhin ang aspect ratio"</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="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_split_screen_text" msgid="449233070804658627">"Mag-drag ng isa pang 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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselahin"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"I-restart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Huwag nang ipakita ulit"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"I-double tap para\nilipat ang app na ito"</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="app_icon_text" msgid="2823268023931811747">"Icon ng App"</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> + <string name="select_text" msgid="5139083974039906583">"Piliin"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string> + <string name="close_text" msgid="4986518933445178928">"Isara"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Buksan ang Menu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index 341d8f113943..b6c0d6864d1c 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Yeniden boyutlandır"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Depola"</string> <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_forced_resizable" msgid="7429086980048964687">"Uygulama bölünmüş ekranda çalışmayabilir"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"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_divider" msgid="6407584574218956849">"Bölünmüş ekran ayırıcı"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Üstte %50"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Üstte %30"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Altta tam ekran"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Sol tarafta böl"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Sağ tarafta böl"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Üst tarafta böl"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Alt tarafta böl"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Tek el modunu kullanma"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Çıkmak için ekranın alt kısmından yukarı kaydırın veya uygulamanın üzerinde herhangi bir yere dokunun"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Tek el modunu başlat"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Anladım"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Son kapatılan baloncuk yok"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Son baloncuklar ve kapattığınız baloncuklar burada görünür"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Baloncukları istediğiniz zaman kontrol edin"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Buraya dokunarak baloncuk olarak gösterilecek uygulama ve görüşmeleri yönetin"</string> <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="6712141648865547958">"Bu uygulamayı yeniden başlatarak daha iyi bir görünüm elde etmek için dokunun."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Bu uygulamayı yeniden başlatarak daha iyi bir görünüm elde etmek için dokunun"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Bu uygulamanın en boy oranını Ayarlar\'dan değiştirin"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"En boy oranını değiştir"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"İptal"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yeniden başlat"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Bir daha gösterme"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Bu uygulamayı taşımak için\niki kez dokunun"</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="app_icon_text" msgid="2823268023931811747">"Uygulama Simgesi"</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> + <string name="select_text" msgid="5139083974039906583">"Seç"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Ekran görüntüsü"</string> + <string name="close_text" msgid="4986518933445178928">"Kapat"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Menüyü Aç"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index 5b017c653e4a..6a119881518a 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"Додаток може не працювати в режимі розділення екрана"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Додаток не підтримує розділення екрана"</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_divider" msgid="6407584574218956849">"Розділювач екрана"</string> + <string name="divider_title" msgid="1963391955593749442">"Розділювач екрана"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхнє вікно на 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Верхнє вікно на 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Нижнє вікно на весь екран"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Розділити зліва"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Розділити справа"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Розділити вгорі"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Розділити внизу"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Як користуватися режимом керування однією рукою"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Щоб вийти, проведіть пальцем по екрану знизу вгору або торкніться екрана над додатком"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Увімкнути режим керування однією рукою"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Контроль спливаючих чатів"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Натисніть тут, щоб вибрати, для яких додатків і розмов дозволити спливаючі чати"</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="6712141648865547958">"Натисніть, щоб перезапустити цей додаток для зручнішого перегляду."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Натисніть, щоб перезапустити цей додаток для зручнішого перегляду"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Змінити формат для цього додатка в налаштуваннях"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Змінити формат"</string> <string name="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="7739895354143295358">"Більше простору та можливостей"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Скасувати"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустити"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Більше не показувати"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Двічі торкніться, щоб\nперемістити цей додаток"</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="app_icon_text" msgid="2823268023931811747">"Значок додатка"</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> + <string name="select_text" msgid="5139083974039906583">"Вибрати"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Знімок екрана"</string> + <string name="close_text" msgid="4986518933445178928">"Закрити"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Відкрити меню"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index 649356951189..292cabae3cdb 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -21,8 +21,8 @@ <string name="pip_phone_expand" msgid="2579292903468287504">"پھیلائیں"</string> <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_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> @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"سائز تبدیل کریں"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string> <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_forced_resizable" msgid="7429086980048964687">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ایپ اسپلٹ اسکرین کو سپورٹ نہیں کرتی ہے"</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_divider" msgid="6407584574218956849">"اسپلٹ اسکرین ڈیوائیڈر"</string> + <string name="divider_title" msgid="1963391955593749442">"اسپلٹ اسکرین ڈیوائیڈر"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"اوپر %50"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"اوپر %30"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"نچلی فل اسکرین"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"دائیں طرف تقسیم کریں"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"بائیں طرف تقسیم کریں"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"اوپر کی طرف تقسیم کریں"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"نیچے کی طرف تقسیم کریں"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ایک ہاتھ کی وضع کا استعمال کرنا"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"باہر نکلنے کیلئے، اسکرین کے نیچے سے اوپر کی طرف سوائپ کریں یا ایپ کے اوپر کہیں بھی تھپتھپائیں"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ایک ہاتھ کی وضع شروع کریں"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"کسی بھی وقت بلبلے کو کنٹرول کریں"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"یہ نظم کرنے کے لیے یہاں تھپتھپائیں کہ کون سی ایپس اور گفتگوئیں بلبلہ سکتی ہیں"</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="6712141648865547958">"بہتر منظر کے لیے اس ایپ کو ری اسٹارٹ کرنے کی خاطر تھپتھپائیں۔"</string> + <string name="restart_button_description" msgid="4564728020654658478">"بہتر منظر کے لیے اس ایپ کو ری اسٹارٹ کرنے کی خاطر تھپتھپائیں"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ترتیبات میں اس ایپ کی تناسبی شرح کو تبدیل کریں"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تناسبی شرح کو تبدیل کریں"</string> <string name="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="7739895354143295358">"دیکھیں اور بہت کچھ کریں"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"منسوخ کریں"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"ری اسٹارٹ کریں"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوبارہ نہ دکھائیں"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"اس ایپ کو منتقل کرنے کیلئے\nدو بار تھپتھپائیں"</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="app_icon_text" msgid="2823268023931811747">"ایپ کا آئیکن"</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> + <string name="select_text" msgid="5139083974039906583">"منتخب کریں"</string> + <string name="screenshot_text" msgid="1477704010087786671">"اسکرین شاٹ"</string> + <string name="close_text" msgid="4986518933445178928">"بند کریں"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"مینیو بند کریں"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"مینو کھولیں"</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 42b9564ff549..d0f011c0b4cf 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml @@ -25,7 +25,7 @@ <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_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> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index fdca0d61e2c4..5f33fe941040 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Oʻlchamini oʻzgartirish"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Berkitish"</string> <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_forced_resizable" msgid="7429086980048964687">"Bu ilovada ekranni ikkiga ajratish rejimi ishlamaydi."</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Bu ilovada ekranni ikkiga ajratish ishlamaydi."</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_divider" msgid="6407584574218956849">"Ekranni ikkiga ajratish chizigʻi"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Tepada 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Tepada 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pastda to‘liq ekran"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Chapga ajratish"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Oʻngga ajratish"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Yuqoriga ajratish"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Pastga ajratish"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ixcham rejimdan foydalanish"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Chiqish uchun ekran pastidan tepaga suring yoki ilovaning tepasidagi istalgan joyga bosing."</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ixcham rejimni ishga tushirish"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Hech qanday bulutcha topilmadi"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Eng oxirgi va yopilgan bulutchali chatlar shu yerda chiqadi"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bulutchalardagi bildirishnomalar"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Bulutchalarda bildirishnomalar chiqishiga ruxsat beruvchi ilova va suhbatlarni tanlang."</string> <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="6712141648865547958">"Yaxshiroq koʻrish maqsadida bu ilovani qayta ishga tushirish uchun bosing."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Yaxshiroq koʻrish maqsadida bu ilovani qayta ishga tushirish uchun bosing"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Sozlamalar orqali bu ilovaning tomonlar nisbatini oʻzgartiring"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tomonlar nisbatini oʻzgartirish"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Bekor qilish"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qaytadan"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Boshqa chiqmasin"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Bu ilovani siljitish uchun\nikki marta bosing"</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="app_icon_text" msgid="2823268023931811747">"Ilova belgisi"</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> + <string name="select_text" msgid="5139083974039906583">"Tanlash"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Skrinshot"</string> + <string name="close_text" msgid="4986518933445178928">"Yopish"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Menyuni ochish"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index 8fd25514b115..29b3b854e3c3 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Đổi kích thước"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ẩn"</string> <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_forced_resizable" msgid="7429086980048964687">"Có thể ứng dụng không dùng được chế độ chia đôi màn hình"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Ứng dụng không hỗ trợ chế độ 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_divider" msgid="6407584574218956849">"Trình chia đôi màn hình"</string> + <string name="divider_title" msgid="1963391955593749442">"Trình chia đôi 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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Trên 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Trên 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Toàn màn hình phía dưới"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Chia đôi màn hình về bên trái"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Chia đôi màn hình về bên phải"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Chia đôi màn hình lên trên cùng"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Chia đôi màn hình xuống dưới cùng"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Cách dùng chế độ một tay"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Để thoát, hãy vuốt lên từ cuối màn hình hoặc nhấn vào vị trí bất kỳ phía trên ứng dụng"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Bắt đầu chế độ một tay"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Đã hiểu"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Không có bong bóng trò chuyện nào gần đây"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Bong bóng trò chuyện đã đóng và bong bóng trò chuyện gần đây sẽ xuất hiện ở đây"</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kiểm soát bong bóng bất cứ lúc nào"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Nhấn vào đây để quản lý việc dùng bong bóng cho các ứng dụng và cuộc trò chuyện"</string> <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="6712141648865547958">"Nhấn để khởi động lại ứng dụng này để xem tốt hơn."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Nhấn nút khởi động lại ứng dụng này để xem dễ hơn"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Thay đổi tỷ lệ khung hình của ứng dụng này thông qua phần Cài đặt"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Thay đổi tỷ lệ khung hình"</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="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_split_screen_text" msgid="449233070804658627">"Kéo một ứng dụng khác vào để 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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Huỷ"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Khởi động lại"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Không hiện lại"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Nhấn đúp để\ndi chuyển ứng dụng này"</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="app_icon_text" msgid="2823268023931811747">"Biểu tượng ứng dụng"</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> + <string name="select_text" msgid="5139083974039906583">"Chọn"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Ảnh chụp màn hình"</string> + <string name="close_text" msgid="4986518933445178928">"Đóng"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Mở Trình đơn"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-watch/colors.xml b/libs/WindowManager/Shell/res/values-watch/colors.xml new file mode 100644 index 000000000000..82492bf2af80 --- /dev/null +++ b/libs/WindowManager/Shell/res/values-watch/colors.xml @@ -0,0 +1,22 @@ +<?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. + */ +--> +<resources> + <color name="splash_window_background_default">@color/splash_screen_bg_dark</color> +</resources> + diff --git a/libs/WindowManager/Shell/res/values-watch/config.xml b/libs/WindowManager/Shell/res/values-watch/config.xml new file mode 100644 index 000000000000..03736edc4ec6 --- /dev/null +++ b/libs/WindowManager/Shell/res/values-watch/config.xml @@ -0,0 +1,35 @@ +<?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. + --> + +<!-- These resources are around just to allow their values to be customized + for watch products. Do not translate. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Animation duration when exit starting window: fade out icon --> + <integer name="starting_window_app_reveal_icon_fade_out_duration">50</integer> + + <!-- Animation delay when exit starting window: reveal app --> + <integer name="starting_window_app_reveal_anim_delay">50</integer> + + <!-- Animation duration when exit starting window: reveal app --> + <integer name="starting_window_app_reveal_anim_duration">200</integer> + + <!-- Default animation type when hiding the starting window. The possible values are: + - 0 for radial vanish + slide up + - 1 for fade out --> + <integer name="starting_window_exit_animation_type">1</integer> +</resources> diff --git a/libs/WindowManager/Shell/res/values-watch/dimen.xml b/libs/WindowManager/Shell/res/values-watch/dimen.xml new file mode 100644 index 000000000000..362e72cb4d2d --- /dev/null +++ b/libs/WindowManager/Shell/res/values-watch/dimen.xml @@ -0,0 +1,22 @@ +<?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. +--> +<resources> + <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (48 X 48) / (72 x 72) --> + <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item> + <!-- Scaling factor applied to splash icons without provided background i.e. (60 / 48) --> + <item type="dimen" format="float" name="splash_icon_no_background_scale_factor">1.25</item> +</resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index ba78d1b8db11..7820965a81c1 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"应用可能无法在分屏模式下正常运行"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"应用不支持分屏"</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_divider" msgid="6407584574218956849">"分屏分隔线"</string> + <string name="divider_title" msgid="1963391955593749442">"分屏分隔线"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"顶部 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"顶部 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"底部全屏"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"左分屏"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"右分屏"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"上分屏"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"下分屏"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用单手模式"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"如需退出,请从屏幕底部向上滑动,或点按应用上方的任意位置"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"启动单手模式"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"随时控制对话泡"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"点按此处即可管理哪些应用和对话可以显示对话泡"</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="6712141648865547958">"点按即可重启此应用,获得更好的视图体验。"</string> + <string name="restart_button_description" msgid="4564728020654658478">"点按即可重启此应用,获得更好的视觉体验"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"在“设置”中更改此应用的宽高比"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"更改高宽比"</string> <string name="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="7739895354143295358">"查看和处理更多任务"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一个应用,即可使用分屏模式"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖入另一个应用,即可使用分屏模式"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"重启"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不再显示"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"点按两次\n即可移动此应用"</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="app_icon_text" msgid="2823268023931811747">"应用图标"</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> + <string name="select_text" msgid="5139083974039906583">"选择"</string> + <string name="screenshot_text" msgid="1477704010087786671">"屏幕截图"</string> + <string name="close_text" msgid="4986518933445178928">"关闭"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"打开菜单"</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 b3bc5b694f50..f0df04a1ea53 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -24,7 +24,7 @@ <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_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> @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"應用程式可能無法在分割螢幕中運作"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"應用程式不支援分割螢幕"</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_divider" msgid="6407584574218956849">"分割螢幕分隔線"</string> + <string name="divider_title" msgid="1963391955593749442">"分割螢幕分隔線"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"頂部 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"頂部 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"底部全螢幕"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"分割左側區域"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"分割右側區域"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"分割上方區域"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"分割下方區域"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用單手模式"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"如要退出,請從螢幕底部向上滑動,或輕按應用程式上方的任何位置"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"開始單手模式"</string> @@ -80,31 +76,46 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"隨時控制對話氣泡"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"輕按這裡即可管理哪些應用程式和對話可以使用對話氣泡"</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="6712141648865547958">"輕按並重新啟動此應用程式,以取得更佳的觀看體驗。"</string> + <string name="restart_button_description" msgid="4564728020654658478">"輕按並重新啟動此應用程式,以取得更佳的觀看體驗"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"前往「設定」變更此應用程式的長寬比"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"變更長寬比"</string> <string name="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="7739895354143295358">"瀏覽更多內容及執行更多操作"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一個應用程式即可分割螢幕"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖入另一個應用程式即可分割螢幕"</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="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動改善檢視畫面嗎?"</string> - <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"您可重新啟動應用程式,讓系統更新檢視畫面;但系統可能不會儲存目前進度及您作出的任何變更"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動以改善檢視畫面嗎?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"你可重新啟動應用程式,讓系統更新檢視畫面;但系統可能不會儲存目前進度及你作出的任何變更"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"輕按兩下\n即可移動此應用程式"</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="app_icon_text" msgid="2823268023931811747">"應用程式圖示"</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> + <string name="select_text" msgid="5139083974039906583">"選取"</string> + <string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string> + <string name="close_text" msgid="4986518933445178928">"關閉"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"打開選單"</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 e7444614a4b0..a9773634ea3d 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -32,13 +32,13 @@ <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_forced_resizable" msgid="7429086980048964687">"應用程式可能無法在分割畫面中運作"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"這個應用程式不支援分割畫面"</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_divider" msgid="6407584574218956849">"分割畫面分隔線"</string> + <string name="divider_title" msgid="1963391955593749442">"分割畫面分隔線"</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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"以 50% 的螢幕空間顯示頂端畫面"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"以 30% 的螢幕空間顯示頂端畫面"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"以全螢幕顯示底部畫面"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"分割左側區域"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"分割右側區域"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"分割上方區域"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"分割下方區域"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用單手模式"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"如要退出,請從螢幕底部向上滑動,或輕觸應用程式上方的任何位置"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"啟動單手模式"</string> @@ -80,15 +76,23 @@ <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> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"你隨時可以控管對話框的各項設定"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"輕觸這裡即可管理哪些應用程式和對話可顯示對話框"</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="6712141648865547958">"請輕觸並重新啟動此應用程式,取得更良好的觀看體驗。"</string> + <string name="restart_button_description" msgid="4564728020654658478">"輕觸此按鈕重新啟動這個應用程式,即可獲得更良好的觀看體驗"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"前往「設定」變更這個應用程式的顯示比例"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"變更顯示比例"</string> <string name="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="7739895354143295358">"瀏覽更多內容及執行更多操作"</string> - <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖進另一個應用程式即可使用分割畫面模式"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖進另一個應用程式即可使用分割畫面模式"</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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"輕觸兩下即可\n移動這個應用程式"</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="app_icon_text" msgid="2823268023931811747">"應用程式圖示"</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> + <string name="select_text" msgid="5139083974039906583">"選取"</string> + <string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string> + <string name="close_text" msgid="4986518933445178928">"關閉"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"開啟選單"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index 913e68f95cb4..a6903a38ecf4 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -32,13 +32,13 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Shintsha usayizi"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Yenza isiteshi"</string> <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_forced_resizable" msgid="7429086980048964687">"Ama-app okungenzeka angasebenzi ngesikrini esihlukanisiwe"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"I-app ayisekeli 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_divider" msgid="6407584574218956849">"Isihlukanisi sokuhlukanisa isikrini"</string> + <string name="divider_title" msgid="1963391955593749442">"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> @@ -49,14 +49,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Okuphezulu okungu-50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Okuphezulu okungu-30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ngaphansi kwesikrini esigcwele"</string> - <!-- no translation found for accessibility_split_left (1713683765575562458) --> - <skip /> - <!-- no translation found for accessibility_split_right (8441001008181296837) --> - <skip /> - <!-- no translation found for accessibility_split_top (2789329702027147146) --> - <skip /> - <!-- no translation found for accessibility_split_bottom (8694551025220868191) --> - <skip /> + <string name="accessibility_split_left" msgid="1713683765575562458">"Hlukanisa ngakwesobunxele"</string> + <string name="accessibility_split_right" msgid="8441001008181296837">"Hlukanisa ngakwesokudla"</string> + <string name="accessibility_split_top" msgid="2789329702027147146">"Hlukanisa phezulu"</string> + <string name="accessibility_split_bottom" msgid="8694551025220868191">"Hlukanisa phansi"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ukusebenzisa imodi yesandla esisodwa"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ukuze uphume, swayipha ngaphezulu kusuka ngezansi kwesikrini noma thepha noma kuphi ngenhla kohlelo lokusebenza"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Qalisa imodi yesandla esisodwa"</string> @@ -80,15 +76,23 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ngiyezwa"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Awekho amabhamuza akamuva"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Amabhamuza akamuva namabhamuza asusiwe azobonakala lapha."</string> + <!-- no translation found for bubble_bar_education_stack_title (2486903590422497245) --> + <skip /> + <!-- no translation found for bubble_bar_education_stack_text (2446934610817409820) --> + <skip /> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Lawula amabhamuza noma nini"</string> + <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Thepha lapha ukuze ulawule ukuthi yimaphi ama-app kanye nezingxoxo ezingenza amabhamuza"</string> <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="6712141648865547958">"Thepha ukuze uqale kabusha le app ukuze ibonakale kangcono."</string> + <string name="restart_button_description" msgid="4564728020654658478">"Thepha ukuze uqale kabusha le app ukuze ibonakale kangcono"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Shintsha ukubukeka kwesilinganiselo kwe-app kuMasethingi"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Shintsha ukubukeka kwesilinganiselo"</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="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_split_screen_text" msgid="449233070804658627">"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> @@ -97,14 +101,21 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Khansela"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qala kabusha"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ungabonisi futhi"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Thepha kabili ukuze\nuhambise le-app"</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="app_icon_text" msgid="2823268023931811747">"Isithonjana Se-app"</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> + <string name="select_text" msgid="5139083974039906583">"Khetha"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Isithombe-skrini"</string> + <string name="close_text" msgid="4986518933445178928">"Vala"</string> + <string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string> + <string name="expand_menu_text" msgid="3847736164494181168">"Vula Imenyu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml index 171a6b2fe5fb..f76a346a8f5d 100644 --- a/libs/WindowManager/Shell/res/values/colors.xml +++ b/libs/WindowManager/Shell/res/values/colors.xml @@ -30,6 +30,9 @@ <color name="bubbles_light">#FFFFFF</color> <color name="bubbles_dark">@color/GM2_grey_800</color> <color name="bubbles_icon_tint">@color/GM2_grey_700</color> + <color name="bubble_bar_expanded_view_handle_light">#EBffffff</color> + <color name="bubble_bar_expanded_view_handle_dark">#99000000</color> + <color name="bubble_bar_expanded_view_menu_close">#DC362E</color> <!-- PiP --> <color name="pip_custom_close_bg">#D93025</color> @@ -61,6 +64,8 @@ <color name="desktop_mode_caption_expand_button_dark">#48473A</color> <color name="desktop_mode_caption_close_button_light">#EFF1F2</color> <color name="desktop_mode_caption_close_button_dark">#1C1C17</color> + <color name="desktop_mode_caption_maximize_button_light">#EFF1F2</color> + <color name="desktop_mode_caption_maximize_button_dark">#1C1C17</color> <color name="desktop_mode_caption_app_name_light">#EFF1F2</color> <color name="desktop_mode_caption_app_name_dark">#1C1C17</color> <color name="desktop_mode_caption_menu_text_color">#191C1D</color> @@ -68,4 +73,8 @@ <color name="desktop_mode_caption_menu_buttons_color_active">#00677E</color> <color name="desktop_mode_resize_veil_light">#EFF1F2</color> <color name="desktop_mode_resize_veil_dark">#1C1C17</color> + <color name="desktop_mode_maximize_menu_button">#DDDACD</color> + <color name="desktop_mode_maximize_menu_button_outline">#797869</color> + <color name="desktop_mode_maximize_menu_button_outline_on_hover">#606219</color> + <color name="desktop_mode_maximize_menu_button_on_hover">#E7E790</color> </resources> diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index a3916b71592b..97a9d4874455 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -83,6 +83,11 @@ <!-- Animation duration when exit starting window: reveal app --> <integer name="starting_window_app_reveal_anim_duration">266</integer> + <!-- Default animation type when hiding the starting window. The possible values are: + - 0 for radial vanish + slide up + - 1 for fade out --> + <integer name="starting_window_exit_animation_type">0</integer> + <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows. These values are in DPs and will be converted to pixel sizes internally. --> <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets"> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 2be34c90a661..cba86c8b3dcf 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -226,10 +226,40 @@ <dimen name="bubble_user_education_padding_end">58dp</dimen> <!-- Padding between the bubble and the user education text. --> <dimen name="bubble_user_education_stack_padding">16dp</dimen> - <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. --> - <dimen name="bubblebar_size">72dp</dimen> - <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. --> - <dimen name="bubblebar_expanded_view_menu_size">16dp</dimen> + <!-- Max width for the bubble popup view. --> + <dimen name="bubble_popup_content_max_width">300dp</dimen> + <!-- Horizontal margin for the bubble popup view. --> + <dimen name="bubble_popup_margin_horizontal">32dp</dimen> + <!-- Top margin for the bubble popup view. --> + <dimen name="bubble_popup_margin_top">16dp</dimen> + <!-- Width for the bubble popup view arrow. --> + <dimen name="bubble_popup_arrow_width">12dp</dimen> + <!-- Height for the bubble popup view arrow. --> + <dimen name="bubble_popup_arrow_height">10dp</dimen> + <!-- Corner radius for the bubble popup view arrow. --> + <dimen name="bubble_popup_arrow_corner_radius">2dp</dimen> + <!-- Padding for the bubble popup view contents. --> + <dimen name="bubble_popup_padding">24dp</dimen> + <!-- The size of the caption bar inset at the top of bubble bar expanded view. --> + <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen> + <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. --> + <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen> + <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. --> + <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen> + <!-- Minimum width of the bubble bar manage menu. --> + <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen> + <!-- Size of the dismiss icon in the bubble bar manage menu. --> + <dimen name="bubble_bar_manage_menu_dismiss_icon_size">16dp</dimen> + <!-- Padding of the bubble bar manage menu, provides space for menu shadows --> + <dimen name="bubble_bar_manage_menu_padding">8dp</dimen> + <!-- Top padding of the bubble bar manage menu --> + <dimen name="bubble_bar_manage_menu_padding_top">2dp</dimen> + <!-- Spacing between sections of the bubble bar manage menu --> + <dimen name="bubble_bar_manage_menu_section_spacing">2dp</dimen> + <!-- Height of an item in the bubble bar manage menu. --> + <dimen name="bubble_bar_manage_menu_item_height">52dp</dimen> + <!-- Size of the icons in the bubble bar manage menu. --> + <dimen name="bubble_bar_manage_menu_item_icon_size">20dp</dimen> <!-- Bottom and end margin for compat buttons. --> <dimen name="compat_button_margin">24dp</dimen> @@ -244,8 +274,8 @@ + compat_button_margin - compat_hint_corner_radius - compat_hint_point_width / 2). --> <dimen name="compat_hint_padding_end">7dp</dimen> - <!-- The width of the size compat hint. --> - <dimen name="size_compat_hint_width">188dp</dimen> + <!-- The width of the compat hint. --> + <dimen name="compat_hint_width">188dp</dimen> <!-- The width of the camera compat hint. --> <dimen name="camera_compat_hint_width">143dp</dimen> @@ -371,6 +401,24 @@ <!-- Height of button (32dp) + 2 * margin (5dp each). --> <dimen name="freeform_decor_caption_height">42dp</dimen> + <!-- The width of the maximize menu in desktop mode. --> + <dimen name="desktop_mode_maximize_menu_width">287dp</dimen> + + <!-- The height of the maximize menu in desktop mode. --> + <dimen name="desktop_mode_maximize_menu_height">112dp</dimen> + + <!-- The larger of the two corner radii of the maximize menu buttons. --> + <dimen name="desktop_mode_maximize_menu_buttons_large_corner_radius">4dp</dimen> + + <!-- The smaller of the two corner radii of the maximize menu buttons. --> + <dimen name="desktop_mode_maximize_menu_buttons_small_corner_radius">2dp</dimen> + + <!-- The corner radius of the maximize menu. --> + <dimen name="desktop_mode_maximize_menu_corner_radius">8dp</dimen> + + <!-- The radius of the Maximize menu shadow. --> + <dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen> + <!-- The width of the handle menu in desktop mode. --> <dimen name="desktop_mode_handle_menu_width">216dp</dimen> @@ -380,9 +428,12 @@ <!-- The height of the handle menu's "Windowing" pill in desktop mode. --> <dimen name="desktop_mode_handle_menu_windowing_pill_height">52dp</dimen> - <!-- The height of the handle menu's "More Actions" pill in desktop mode. --> + <!-- The height of the handle menu's "More Actions" pill in desktop mode, but not freeform. --> <dimen name="desktop_mode_handle_menu_more_actions_pill_height">156dp</dimen> + <!-- The height of the handle menu's "More Actions" pill in freeform desktop windowing mode. --> + <dimen name="desktop_mode_handle_menu_more_actions_pill_freeform_height">104dp</dimen> + <!-- The top margin of the handle menu in desktop mode. --> <dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen> @@ -398,7 +449,20 @@ <!-- The radius of the caption menu shadow. --> <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen> - <dimen name="freeform_resize_handle">30dp</dimen> + <dimen name="freeform_resize_handle">15dp</dimen> <dimen name="freeform_resize_corner">44dp</dimen> + + <!-- The width of the area at the sides of the screen where a freeform task will transition to + split select if dragged until the touch input is within the range. --> + <dimen name="desktop_mode_transition_area_width">32dp</dimen> + + <!-- The height of the area at the top of the screen where a freeform task will transition to + fullscreen if dragged until the top bound of the task is within the area. --> + <dimen name="desktop_mode_transition_area_height">16dp</dimen> + + <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) --> + <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item> + <!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) --> + <item type="dimen" format="float" name="splash_icon_no_background_scale_factor">1.2</item> </resources> diff --git a/libs/WindowManager/Shell/res/values/ids.xml b/libs/WindowManager/Shell/res/values/ids.xml index 8831b610f44a..bc59a235517d 100644 --- a/libs/WindowManager/Shell/res/values/ids.xml +++ b/libs/WindowManager/Shell/res/values/ids.xml @@ -42,4 +42,6 @@ <item type="id" name="action_move_top_right"/> <item type="id" name="action_move_bottom_left"/> <item type="id" name="action_move_bottom_right"/> + + <item type="id" name="dismiss_view"/> </resources> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index b192fdf245e2..b556150e2ab9 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -142,6 +142,10 @@ <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string> <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]--> <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string> + <!-- Accessibility announcement when the stack of bubbles expands. [CHAR LIMIT=NONE]--> + <string name="bubble_accessibility_announce_expand">expand <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string> + <!-- Accessibility announcement when the stack of bubbles collapses. [CHAR LIMIT=NONE]--> + <string name="bubble_accessibility_announce_collapse">collapse <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string> <!-- Label for the button that takes the user to the notification settings for the given app. --> <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string> <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] --> @@ -163,6 +167,15 @@ <!-- [CHAR LIMIT=NONE] Empty overflow subtitle --> <string name="bubble_overflow_empty_subtitle">Recent bubbles and dismissed bubbles will appear here</string> + <!-- Title text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]--> + <string name="bubble_bar_education_stack_title">Chat using bubbles</string> + <!-- Descriptive text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=NONE] --> + <string name="bubble_bar_education_stack_text">New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them.</string> + <!-- Title text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=60]--> + <string name="bubble_bar_education_manage_title">Control bubbles anytime</string> + <!-- Descriptive text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=80]--> + <string name="bubble_bar_education_manage_text">Tap here to manage which apps and conversations can bubble</string> + <!-- [CHAR LIMIT=100] Notification Importance title --> <string name="notification_bubble_title">Bubble</string> @@ -173,7 +186,13 @@ <string name="accessibility_bubble_dismissed">Bubble dismissed.</string> <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] --> - <string name="restart_button_description">Tap to restart this app for a better view.</string> + <string name="restart_button_description">Tap to restart this app for a better view</string> + + <!-- Tooltip text of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] --> + <string name="user_aspect_ratio_settings_button_hint">Change this app\'s aspect ratio in Settings</string> + + <!-- Content description of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] --> + <string name="user_aspect_ratio_settings_button_description">Change aspect ratio</string> <!-- Description of the camera compat button for applying stretched issues treatment in the hint for compatibility control. [CHAR LIMIT=NONE] --> 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 9aac694e41bf..04795768aefc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -103,7 +103,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {} /** Whether this task listener supports compat UI. */ default boolean supportCompatUI() { - // All TaskListeners should support compat UI except PIP. + // All TaskListeners should support compat UI except PIP and StageCoordinator. return true; } /** Attaches a child window surface to the task surface. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java index 39f861de1ba0..5cf9175073c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java @@ -50,7 +50,7 @@ class ActivityEmbeddingAnimationAdapter { final SurfaceControl mLeash; /** Area in absolute coordinate that the animation surface shouldn't go beyond. */ @NonNull - private final Rect mWholeAnimationBounds = new Rect(); + final Rect mWholeAnimationBounds = new Rect(); /** * Area in absolute coordinate that should represent all the content to show for this window. * This should be the end bounds for opening window, and start bounds for closing window in case @@ -229,20 +229,7 @@ class ActivityEmbeddingAnimationAdapter { mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y); t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); t.setAlpha(mLeash, mTransformation.getAlpha()); - - // The following applies an inverse scale to the clip-rect so that it crops "after" the - // scale instead of before. - mVecs[1] = mVecs[2] = 0; - mVecs[0] = mVecs[3] = 1; - mTransformation.getMatrix().mapVectors(mVecs); - mVecs[0] = 1.f / mVecs[0]; - mVecs[3] = 1.f / mVecs[3]; - final Rect clipRect = mTransformation.getClipRect(); - mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f); - mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f); - mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f); - mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f); - t.setCrop(mLeash, mRect); + t.setWindowCrop(mLeash, mWholeAnimationBounds.width(), mWholeAnimationBounds.height()); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index 1793a3d0feb4..4640106b5f1c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -26,7 +26,6 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; -import android.view.animation.ClipRectAnimation; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.ScaleAnimation; @@ -189,14 +188,6 @@ class ActivityEmbeddingAnimationSpec { startBounds.top - endBounds.top, 0); endTranslate.setDuration(CHANGE_ANIMATION_DURATION); endSet.addAnimation(endTranslate); - // The end leash is resizing, we should update the window crop based on the clip rect. - final Rect startClip = new Rect(startBounds); - final Rect endClip = new Rect(endBounds); - startClip.offsetTo(0, 0); - endClip.offsetTo(0, 0); - final Animation clipAnim = new ClipRectAnimation(startClip, endClip); - clipAnim.setDuration(CHANGE_ANIMATION_DURATION); - endSet.addAnimation(clipAnim); endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(), parentBounds.height()); endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 57d374b2b8f5..06ce37148eaf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -186,6 +186,6 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle if (callback == null) { throw new IllegalStateException("No finish callback found"); } - callback.onTransitionFinished(null /* wct */, null /* wctCB */); + callback.onTransitionFinished(null /* wct */); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java index 798250de89d0..26edd7d2268b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java @@ -117,6 +117,20 @@ public class FlingAnimationUtils { * @param endValue the end value of the animator * @param velocity the current velocity of the motion */ + public void apply(androidx.core.animation.Animator animator, + float currValue, float endValue, float velocity) { + apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion + */ public void apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity) { apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); @@ -152,6 +166,24 @@ public class FlingAnimationUtils { * @param maxDistance the maximum distance for this interaction; the maximum animation length * gets multiplied by the ratio between the actual distance and this value */ + public void apply(androidx.core.animation.Animator animator, + float currValue, float endValue, float velocity, float maxDistance) { + AnimatorProperties properties = getProperties(currValue, endValue, velocity, maxDistance); + animator.setDuration(properties.mDuration); + animator.setInterpolator(properties.getInterpolator()); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion + * @param maxDistance the maximum distance for this interaction; the maximum animation length + * gets multiplied by the ratio between the actual distance and this value + */ public void apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance) { AnimatorProperties properties = getProperties(currValue, endValue, velocity, @@ -367,6 +399,11 @@ public class FlingAnimationUtils { private static class AnimatorProperties { Interpolator mInterpolator; long mDuration; + + /** Get an AndroidX interpolator wrapper of the current mInterpolator */ + public androidx.core.animation.Interpolator getInterpolator() { + return mInterpolator::getInterpolation; + } } /** Builder for {@link #FlingAnimationUtils}. */ 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 index 9bf3b80d262e..42dc19ce838a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java @@ -52,12 +52,13 @@ public class BackAnimationBackground { /** * Ensures the back animation background color layer is present. + * * @param startRect The start bounds of the closing target. * @param color The background color. * @param transaction The animation transaction. */ - void ensureBackground(Rect startRect, int color, - @NonNull SurfaceControl.Transaction transaction) { + public void ensureBackground( + Rect startRect, int color, @NonNull SurfaceControl.Transaction transaction) { if (mBackgroundSurface != null) { return; } @@ -81,7 +82,12 @@ public class BackAnimationBackground { mIsRequestingStatusBarAppearance = false; } - void removeBackground(@NonNull SurfaceControl.Transaction transaction) { + /** + * Remove the back animation background. + * + * @param transaction The animation transaction. + */ + public void removeBackground(@NonNull SurfaceControl.Transaction transaction) { if (mBackgroundSurface == null) { return; } @@ -93,11 +99,21 @@ public class BackAnimationBackground { mIsRequestingStatusBarAppearance = false; } + /** + * Attach a {@link StatusBarCustomizer} instance to allow status bar animate with back progress. + * + * @param customizer The {@link StatusBarCustomizer} to be used. + */ void setStatusBarCustomizer(StatusBarCustomizer customizer) { mCustomizer = customizer; } - void onBackProgressed(float progress) { + /** + * Update back animation background with for the progress. + * + * @param progress Progress value from {@link android.window.BackProgressAnimator} + */ + public void onBackProgressed(float progress) { if (mCustomizer == null || mStartBounds.isEmpty()) { return; } 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 bb543f24a8ea..3790f04b56eb 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 @@ -25,6 +25,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.content.ContentResolver; @@ -43,7 +44,6 @@ import android.provider.Settings.Global; import android.util.DisplayMetrics; import android.util.Log; import android.util.MathUtils; -import android.util.SparseArray; import android.view.IRemoteAnimationRunner; import android.view.InputDevice; import android.view.KeyCharacterMap; @@ -70,6 +70,7 @@ import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; + import java.util.concurrent.atomic.AtomicBoolean; /** @@ -113,7 +114,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private boolean mShouldStartOnNextMoveEvent = false; /** @see #setTriggerBack(boolean) */ private boolean mTriggerBack; - private FlingAnimationUtils mFlingAnimationUtils; + + private final FlingAnimationUtils mFlingAnimationUtils; + + /** Registry for the back animations */ + private final ShellBackAnimationRegistry mShellBackAnimationRegistry; @Nullable private BackNavigationInfo mBackNavigationInfo; @@ -135,13 +140,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private final TouchTracker mTouchTracker = new TouchTracker(); - private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>(); @Nullable private IOnBackInvokedCallback mActiveCallback; - private CrossActivityAnimation mDefaultActivityAnimation; - private CustomizeActivityAnimation mCustomizeActivityAnimation; - @VisibleForTesting final RemoteCallback mNavigationObserver = new RemoteCallback( new RemoteCallback.OnResultListener() { @@ -169,10 +170,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler backgroundHandler, Context context, - @NonNull BackAnimationBackground backAnimationBackground) { - this(shellInit, shellController, shellExecutor, backgroundHandler, - ActivityTaskManager.getService(), context, context.getContentResolver(), - backAnimationBackground); + @NonNull BackAnimationBackground backAnimationBackground, + ShellBackAnimationRegistry shellBackAnimationRegistry) { + this( + shellInit, + shellController, + shellExecutor, + backgroundHandler, + ActivityTaskManager.getService(), + context, + context.getContentResolver(), + backAnimationBackground, + shellBackAnimationRegistry); } @VisibleForTesting @@ -182,8 +191,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler bgHandler, @NonNull IActivityTaskManager activityTaskManager, - Context context, ContentResolver contentResolver, - @NonNull BackAnimationBackground backAnimationBackground) { + Context context, + ContentResolver contentResolver, + @NonNull BackAnimationBackground backAnimationBackground, + ShellBackAnimationRegistry shellBackAnimationRegistry) { mShellController = shellController; mShellExecutor = shellExecutor; mActivityTaskManager = activityTaskManager; @@ -197,11 +208,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS) .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) .build(); - } - - @VisibleForTesting - void setEnableUAnimation(boolean enable) { - IS_U_ANIMATION_ENABLED = enable; + mShellBackAnimationRegistry = shellBackAnimationRegistry; } private void onInit() { @@ -209,26 +216,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont 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); - mDefaultActivityAnimation = - new CrossActivityAnimation(mContext, mAnimationBackground); - mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, - mDefaultActivityAnimation.mBackAnimationRunner); - mCustomizeActivityAnimation = - new CustomizeActivityAnimation(mContext, mAnimationBackground); - // TODO (236760237): register dialog close animation when it's completed. } private void setupAnimationDeveloperSettingsObserver( @@ -359,11 +346,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont void registerAnimation(@BackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner) { - mAnimationDefinition.set(type, runner); + mShellBackAnimationRegistry.registerAnimation(type, runner); } void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) { - mAnimationDefinition.remove(type); + mShellBackAnimationRegistry.unregisterAnimation(type); } /** @@ -434,9 +421,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont final int backType = backNavigationInfo.getType(); final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(); if (shouldDispatchToAnimator) { - if (mAnimationDefinition.contains(backType)) { - mAnimationDefinition.get(backType).startGesture(); - } else { + if (!mShellBackAnimationRegistry.startGesture(backType)) { mActiveCallback = null; } } else { @@ -459,6 +444,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont sendBackEvent(KeyEvent.ACTION_UP); } + @SuppressLint("MissingPermission") private void sendBackEvent(int action) { final long when = SystemClock.uptimeMillis(); final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */, @@ -671,21 +657,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } final int backType = mBackNavigationInfo.getType(); - final BackAnimationRunner runner = mAnimationDefinition.get(backType); // Simply trigger and finish back navigation when no animator defined. - if (!shouldDispatchToAnimator() || runner == null) { + if (!shouldDispatchToAnimator() + || mShellBackAnimationRegistry.isAnimationCancelledOrNull(backType)) { invokeOrCancelBack(); return; - } - if (runner.isWaitingAnimation()) { + } else if (mShellBackAnimationRegistry.isWaitingAnimation(backType)) { ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready."); // Supposed it is in post commit animation state, and start the timeout to watch // if the animation is ready. mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); return; - } else if (runner.isAnimationCancelled()) { - invokeOrCancelBack(); - return; } startPostCommitAnimation(); } @@ -737,12 +719,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShouldStartOnNextMoveEvent = false; mTouchTracker.reset(); mActiveCallback = null; - // reset to default - if (mDefaultActivityAnimation != null - && mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) { - mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, - mDefaultActivityAnimation.mBackAnimationRunner); - } + mShellBackAnimationRegistry.resetDefaultCrossActivity(); if (mBackNavigationInfo != null) { mBackNavigationInfo.onBackNavigationFinished(mTriggerBack); mBackNavigationInfo = null; @@ -750,86 +727,88 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTriggerBack = false; } - private BackAnimationRunner getAnimationRunnerAndInit() { - int type = mBackNavigationInfo.getType(); - // Initiate customized cross-activity animation, or fall back to cross activity animation - if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) { - final BackNavigationInfo.CustomAnimationInfo animationInfo = - mBackNavigationInfo.getCustomAnimationInfo(); - if (animationInfo != null && mCustomizeActivityAnimation != null - && mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) { - mAnimationDefinition.get(type).resetWaitingAnimation(); - mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, - mCustomizeActivityAnimation.mBackAnimationRunner); - } - } - return mAnimationDefinition.get(type); - } private void createAdapter() { - IBackAnimationRunner runner = new IBackAnimationRunner.Stub() { - @Override - public void onAnimationStart(RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, - IBackAnimationFinishedCallback finishedCallback) { - mShellExecutor.execute(() -> { - if (mBackNavigationInfo == null) { - Log.e(TAG, "Lack of navigation info to start animation."); - return; + IBackAnimationRunner runner = + new IBackAnimationRunner.Stub() { + @Override + public void onAnimationStart( + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + IBackAnimationFinishedCallback finishedCallback) { + mShellExecutor.execute( + () -> { + if (mBackNavigationInfo == null) { + Log.e(TAG, "Lack of navigation info to start animation."); + return; + } + final BackAnimationRunner runner = + mShellBackAnimationRegistry.getAnimationRunnerAndInit( + mBackNavigationInfo); + if (runner == null) { + if (finishedCallback != null) { + try { + finishedCallback.onAnimationFinished(false); + } catch (RemoteException e) { + Log.w( + TAG, + "Failed call IBackNaviAnimationController", + e); + } + } + return; + } + mActiveCallback = runner.getCallback(); + 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])); + } + + // Dispatch the first progress after animation start for + // smoothing the initial animation, instead of waiting for next + // onMove. + final BackMotionEvent backFinish = + mTouchTracker.createProgressEvent(); + dispatchOnBackProgressed(mActiveCallback, backFinish); + if (!mBackGestureStarted) { + // if the down -> up gesture happened before animation + // start, we have to trigger the uninterruptible transition + // to finish the back animation. + startPostCommitAnimation(); + } + }); } - final int type = mBackNavigationInfo.getType(); - final BackAnimationRunner runner = getAnimationRunnerAndInit(); - 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; - } - mActiveCallback = runner.getCallback(); - 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])); + @Override + public void onAnimationCancelled() { + mShellExecutor.execute( + () -> { + if (!mShellBackAnimationRegistry.cancel( + mBackNavigationInfo.getType())) { + return; + } + if (!mBackGestureStarted) { + invokeOrCancelBack(); + } + }); } - - // Dispatch the first progress after animation start for smoothing the initial - // animation, instead of waiting for next onMove. - final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(); - dispatchOnBackProgressed(mActiveCallback, backFinish); - if (!mBackGestureStarted) { - // if the down -> up gesture happened before animation start, we have to - // trigger the uninterruptible transition to finish the back animation. - startPostCommitAnimation(); - } - }); - } - - @Override - public void onAnimationCancelled() { - mShellExecutor.execute(() -> { - final BackAnimationRunner runner = mAnimationDefinition.get( - mBackNavigationInfo.getType()); - if (runner == null) { - return; - } - runner.cancelAnimation(); - if (!mBackGestureStarted) { - invokeOrCancelBack(); - } - }); - } - }; + }; 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 index 913239f74bf2..431df212f099 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java @@ -32,7 +32,7 @@ import android.window.IOnBackInvokedCallback; * before it received IBackAnimationRunner#onAnimationStart, so the controller could continue * trigger the real back behavior. */ -class BackAnimationRunner { +public class BackAnimationRunner { private static final String TAG = "ShellBackPreview"; private final IOnBackInvokedCallback mCallback; @@ -44,8 +44,8 @@ class BackAnimationRunner { /** True when the back animation is cancelled */ private boolean mAnimationCancelled; - BackAnimationRunner(@NonNull IOnBackInvokedCallback callback, - @NonNull IRemoteAnimationRunner runner) { + public BackAnimationRunner( + @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner) { mCallback = callback; mRunner = runner; } 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 index edefe9e3ab06..114486e848f0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java @@ -51,9 +51,11 @@ import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.annotations.ShellMainThread; +import javax.inject.Inject; + /** Class that defines cross-activity animation. */ @ShellMainThread -class CrossActivityAnimation { +public class CrossActivityAnimation extends ShellBackAnimation { /** * Minimum scale of the entering/closing window. */ @@ -106,6 +108,7 @@ class CrossActivityAnimation { private final SpringAnimation mLeavingProgressSpring; // Max window x-shift in pixels. private final float mWindowXShift; + private final BackAnimationRunner mBackAnimationRunner; private float mEnteringProgress = 0f; private float mLeavingProgress = 0f; @@ -126,11 +129,11 @@ class CrossActivityAnimation { private IRemoteAnimationFinishedCallback mFinishCallback; private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); - final BackAnimationRunner mBackAnimationRunner; private final BackAnimationBackground mBackground; - CrossActivityAnimation(Context context, BackAnimationBackground background) { + @Inject + public CrossActivityAnimation(Context context, BackAnimationBackground background) { mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner()); mBackground = background; @@ -357,6 +360,11 @@ class CrossActivityAnimation { mTransaction.apply(); } + @Override + public BackAnimationRunner getRunner() { + return mBackAnimationRunner; + } + private final class Callback extends IOnBackInvokedCallback.Default { @Override public void onBackStarted(BackMotionEvent backEvent) { @@ -371,7 +379,15 @@ class CrossActivityAnimation { @Override public void onBackCancelled() { - mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation); + mProgressAnimator.onBackCancelled(() -> { + // mProgressAnimator can reach finish stage earlier than mLeavingProgressSpring, + // and if we release all animation leash first, the leavingProgressSpring won't + // able to update the animation anymore, which cause flicker. + // Here should force update the closing animation target to the final stage before + // release it. + setLeavingProgress(0); + finishAnimation(); + }); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index a7dd27a0784f..209d8533ee7d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -47,21 +47,23 @@ import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.annotations.ShellMainThread; +import javax.inject.Inject; + /** * 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. + * <p>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. + * <p>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 { +public class CrossTaskBackAnimation extends ShellBackAnimation { private static final int BACKGROUNDCOLOR = 0x43433A; /** @@ -104,28 +106,41 @@ class CrossTaskBackAnimation { private final float[] mTmpFloat9 = new float[9]; private final float[] mTmpTranslate = {0, 0, 0}; - + private final BackAnimationRunner mBackAnimationRunner; + private final BackAnimationBackground mBackground; 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) { + @Inject + public CrossTaskBackAnimation(Context context, BackAnimationBackground background) { mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner()); mBackground = background; } + private 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 static float mapRange(float value, float min, float max) { + return min + (value * (max - min)); + } + private float getInterpolatedProgress(float backProgress) { return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress); } @@ -233,18 +248,6 @@ class CrossTaskBackAnimation { 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(); @@ -314,11 +317,12 @@ class CrossTaskBackAnimation { valueAnimator.start(); } - private static float mapRange(float value, float min, float max) { - return min + (value * (max - min)); + @Override + public BackAnimationRunner getRunner() { + return mBackAnimationRunner; } - private final class Callback extends IOnBackInvokedCallback.Default { + private final class Callback extends IOnBackInvokedCallback.Default { @Override public void onBackStarted(BackMotionEvent backEvent) { mProgressAnimator.onBackStarted(backEvent, @@ -340,7 +344,7 @@ class CrossTaskBackAnimation { mProgressAnimator.reset(); onGestureCommitted(); } - }; + } private final class Runner extends IRemoteAnimationRunner.Default { @Override @@ -360,5 +364,5 @@ class CrossTaskBackAnimation { startBackAnimation(); mFinishCallback = finishedCallback; } - }; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java index 2d6ec7547187..aca638c1a5cf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java @@ -55,13 +55,13 @@ import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.annotations.ShellMainThread; -/** - * Class that handle customized close activity transition animation. - */ +import javax.inject.Inject; + +/** Class that handle customized close activity transition animation. */ @ShellMainThread -class CustomizeActivityAnimation { +public class CustomizeActivityAnimation extends ShellBackAnimation { private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); - final BackAnimationRunner mBackAnimationRunner; + private final BackAnimationRunner mBackAnimationRunner; private final float mCornerRadius; private final SurfaceControl.Transaction mTransaction; private final BackAnimationBackground mBackground; @@ -88,7 +88,8 @@ class CustomizeActivityAnimation { private final Choreographer mChoreographer; - CustomizeActivityAnimation(Context context, BackAnimationBackground background) { + @Inject + public CustomizeActivityAnimation(Context context, BackAnimationBackground background) { this(context, background, new SurfaceControl.Transaction(), null); } @@ -258,10 +259,12 @@ class CustomizeActivityAnimation { valueAnimator.start(); } - /** - * Load customize animation before animation start. - */ - boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) { + /** Load customize animation before animation start. */ + @Override + public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) { + if (animationInfo == null) { + return false; + } final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo); if (result != null) { mCloseAnimation = result.mCloseAnimation; @@ -272,6 +275,11 @@ class CustomizeActivityAnimation { return false; } + @Override + public BackAnimationRunner getRunner() { + return mBackAnimationRunner; + } + private final class Callback extends IOnBackInvokedCallback.Default { @Override public void onBackStarted(BackMotionEvent backEvent) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java new file mode 100644 index 000000000000..312e88db863e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java @@ -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.back; + +import android.window.BackNavigationInfo; + +import javax.inject.Qualifier; + +/** Base class for all back animations. */ +public abstract class ShellBackAnimation { + @Qualifier + public @interface CrossActivity {} + + @Qualifier + public @interface CrossTask {} + + @Qualifier + public @interface CustomizeActivity {} + + @Qualifier + public @interface ReturnToHome {} + + /** Retrieve the {@link BackAnimationRunner} associated with this animation. */ + public abstract BackAnimationRunner getRunner(); + + /** + * Prepare the next animation with customized animation. + * + * @return true if this type of back animation should override the default. + */ + public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) { + return false; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java new file mode 100644 index 000000000000..62b18f342995 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java @@ -0,0 +1,145 @@ +/* + * 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.back; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; +import android.util.SparseArray; +import android.window.BackNavigationInfo; + +/** Registry for all types of default back animations */ +public class ShellBackAnimationRegistry { + private static final String TAG = "ShellBackPreview"; + + private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>(); + private final ShellBackAnimation mDefaultCrossActivityAnimation; + private final ShellBackAnimation mCustomizeActivityAnimation; + + public ShellBackAnimationRegistry( + @ShellBackAnimation.CrossActivity @Nullable ShellBackAnimation crossActivityAnimation, + @ShellBackAnimation.CrossTask @Nullable ShellBackAnimation crossTaskAnimation, + @ShellBackAnimation.CustomizeActivity @Nullable + ShellBackAnimation customizeActivityAnimation, + @ShellBackAnimation.ReturnToHome @Nullable + ShellBackAnimation defaultBackToHomeAnimation) { + if (crossActivityAnimation != null) { + mAnimationDefinition.set( + BackNavigationInfo.TYPE_CROSS_TASK, crossTaskAnimation.getRunner()); + } + if (crossActivityAnimation != null) { + mAnimationDefinition.set( + BackNavigationInfo.TYPE_CROSS_ACTIVITY, crossActivityAnimation.getRunner()); + } + if (defaultBackToHomeAnimation != null) { + mAnimationDefinition.set( + BackNavigationInfo.TYPE_RETURN_TO_HOME, defaultBackToHomeAnimation.getRunner()); + } + + mDefaultCrossActivityAnimation = crossActivityAnimation; + mCustomizeActivityAnimation = customizeActivityAnimation; + + // TODO(b/236760237): register dialog close animation when it's completed. + } + + void registerAnimation( + @BackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner) { + mAnimationDefinition.set(type, runner); + } + + void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) { + mAnimationDefinition.remove(type); + } + + /** + * Start the {@link BackAnimationRunner} associated with a back target type. + * + * @param type back target type + * @return true if the animation is started, false if animation is not found for that type. + */ + boolean startGesture(@BackNavigationInfo.BackTargetType int type) { + BackAnimationRunner runner = mAnimationDefinition.get(type); + if (runner == null) { + return false; + } + runner.startGesture(); + return true; + } + + /** + * Cancel the {@link BackAnimationRunner} associated with a back target type. + * + * @param type back target type + * @return true if the animation is started, false if animation is not found for that type. + */ + boolean cancel(@BackNavigationInfo.BackTargetType int type) { + BackAnimationRunner runner = mAnimationDefinition.get(type); + if (runner == null) { + return false; + } + runner.cancelAnimation(); + return true; + } + + boolean isAnimationCancelledOrNull(@BackNavigationInfo.BackTargetType int type) { + BackAnimationRunner runner = mAnimationDefinition.get(type); + if (runner == null) { + return true; + } + return runner.isAnimationCancelled(); + } + + boolean isWaitingAnimation(@BackNavigationInfo.BackTargetType int type) { + BackAnimationRunner runner = mAnimationDefinition.get(type); + if (runner == null) { + return false; + } + return runner.isWaitingAnimation(); + } + + void resetDefaultCrossActivity() { + if (mDefaultCrossActivityAnimation == null + || !mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) { + return; + } + mAnimationDefinition.set( + BackNavigationInfo.TYPE_CROSS_ACTIVITY, mDefaultCrossActivityAnimation.getRunner()); + } + + BackAnimationRunner getAnimationRunnerAndInit(BackNavigationInfo backNavigationInfo) { + int type = backNavigationInfo.getType(); + // Initiate customized cross-activity animation, or fall back to cross activity animation + if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) { + if (mCustomizeActivityAnimation != null + && mCustomizeActivityAnimation.prepareNextAnimation( + backNavigationInfo.getCustomAnimationInfo())) { + mAnimationDefinition.get(type).resetWaitingAnimation(); + mAnimationDefinition.set( + BackNavigationInfo.TYPE_CROSS_ACTIVITY, + mCustomizeActivityAnimation.getRunner()); + } + } + BackAnimationRunner runner = mAnimationDefinition.get(type); + if (runner == null) { + Log.e( + TAG, + "Animation didn't be defined for type " + + BackNavigationInfo.typeToString(type)); + } + return runner; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING index 837d5ff3b073..f02559f36169 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING @@ -12,19 +12,19 @@ ] }, { - "name": "CtsWindowManagerDeviceTestCases", + "name": "CtsWindowManagerDeviceBackNavigation", "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { - "include-filter": "android.server.wm.BackGestureInvokedTest" + "include-filter": "android.server.wm.backnavigation.BackGestureInvokedTest" }, { - "include-filter": "android.server.wm.BackNavigationTests" + "include-filter": "android.server.wm.backnavigation.BackNavigationTests" }, { - "include-filter": "android.server.wm.OnBackInvokedCallbackGestureTest" + "include-filter": "android.server.wm.backnavigation.OnBackInvokedCallbackGestureTest" } ] } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 102f2cb4b8d0..85ea8097a2c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -60,7 +60,6 @@ import java.util.concurrent.Executor; /** * Encapsulates the data and UI elements of a bubble. */ -@VisibleForTesting public class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; @@ -422,6 +421,7 @@ public class Bubble implements BubbleViewProvider { } if (mBubbleBarExpandedView != null) { mBubbleBarExpandedView.cleanUpExpandedState(); + mBubbleBarExpandedView = null; } if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); @@ -549,10 +549,10 @@ public class Bubble implements BubbleViewProvider { /** * Set visibility of bubble in the expanded state. * - * @param visibility {@code true} if the expanded bubble should be visible on the screen. - * - * Note that this contents visibility doesn't affect visibility at {@link android.view.View}, + * <p>Note that this contents visibility doesn't affect visibility at {@link android.view.View}, * and setting {@code false} actually means rendering the expanded view in transparent. + * + * @param visibility {@code true} if the expanded bubble should be visible on the screen. */ @Override public void setTaskViewVisibility(boolean visibility) { @@ -851,11 +851,15 @@ public class Bubble implements BubbleViewProvider { return mAppIntent; } - boolean isAppBubble() { + /** + * Returns whether this bubble is from an app versus a notification. + */ + public boolean isAppBubble() { return mIsAppBubble; } - Intent getSettingsIntent(final Context context) { + /** Creates open app settings intent */ + public Intent getSettingsIntent(final Context context) { final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); final int uid = getUid(context); @@ -969,9 +973,9 @@ public class Bubble implements BubbleViewProvider { pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification()); pw.print(" autoExpand: "); pw.println(shouldAutoExpand()); pw.print(" isDismissable: "); pw.println(mIsDismissable); - pw.println(" bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null)); + pw.println(" bubbleMetadataFlagListener null?: " + (mBubbleMetadataFlagListener == null)); if (mExpandedView != null) { - mExpandedView.dump(pw); + mExpandedView.dump(pw, " "); } } 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 0fdfbb8c0c61..f259902e9565 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 @@ -16,7 +16,6 @@ package com.android.wm.shell.bubbles; -import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; @@ -25,7 +24,6 @@ import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED; @@ -37,6 +35,7 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES; import android.annotation.BinderThread; @@ -57,6 +56,7 @@ import android.content.pm.ShortcutInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Icon; import android.os.Binder; @@ -64,7 +64,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.service.notification.NotificationListenerService; @@ -86,12 +85,14 @@ import androidx.annotation.MainThread; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; import com.android.internal.statusbar.IStatusBarService; import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; +import com.android.wm.shell.bubbles.properties.BubbleProperties; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.FloatingContentCoordinator; @@ -114,6 +115,7 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.taskview.TaskView; import com.android.wm.shell.taskview.TaskViewTransitions; +import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; import java.util.ArrayList; @@ -142,16 +144,8 @@ public class BubbleController implements ConfigurationChangeListener, // Should match with PhoneWindowManager private static final String SYSTEM_DIALOG_REASON_KEY = "reason"; private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav"; - - // TODO(b/256873975) Should use proper flag when available to shell/launcher - /** - * Whether bubbles are showing in the bubble bar from launcher. This is only available - * on large screens and {@link BubbleController#isShowingAsBubbleBar()} should be used - * to check all conditions that indicate if the bubble bar is in use. - */ - private static final boolean BUBBLE_BAR_ENABLED = - SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); - + private static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; + private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; /** * Common interface to send updates to bubble views. @@ -191,10 +185,12 @@ public class BubbleController implements ConfigurationChangeListener, private final ShellTaskOrganizer mTaskOrganizer; private final DisplayController mDisplayController; private final TaskViewTransitions mTaskViewTransitions; + private final Transitions mTransitions; private final SyncTransactionQueue mSyncQueue; private final ShellController mShellController; private final ShellCommandHandler mShellCommandHandler; private final IWindowManager mWmService; + private final BubbleProperties mBubbleProperties; // Used to post to main UI thread private final ShellExecutor mMainExecutor; @@ -290,8 +286,10 @@ public class BubbleController implements ConfigurationChangeListener, @ShellMainThread Handler mainHandler, @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewTransitions taskViewTransitions, + Transitions transitions, SyncTransactionQueue syncQueue, - IWindowManager wmService) { + IWindowManager wmService, + BubbleProperties bubbleProperties) { mContext = context; mShellCommandHandler = shellCommandHandler; mShellController = shellController; @@ -324,10 +322,12 @@ public class BubbleController implements ConfigurationChangeListener, com.android.internal.R.dimen.importance_ring_stroke_width)); mDisplayController = displayController; mTaskViewTransitions = taskViewTransitions; + mTransitions = transitions; mOneHandedOptional = oneHandedOptional; mDragAndDropController = dragAndDropController; mSyncQueue = syncQueue; mWmService = wmService; + mBubbleProperties = bubbleProperties; shellInit.addInitCallback(this::onInit, this); } @@ -336,16 +336,20 @@ public class BubbleController implements ConfigurationChangeListener, new OneHandedTransitionCallback() { @Override public void onStartFinished(Rect bounds) { - if (mStackView != null) { - mStackView.onVerticalOffsetChanged(bounds.top); - } + mMainExecutor.execute(() -> { + if (mStackView != null) { + mStackView.onVerticalOffsetChanged(bounds.top); + } + }); } @Override public void onStopFinished(Rect bounds) { - if (mStackView != null) { - mStackView.onVerticalOffsetChanged(bounds.top); - } + mMainExecutor.execute(() -> { + if (mStackView != null) { + mStackView.onVerticalOffsetChanged(bounds.top); + } + }); } }); } @@ -418,23 +422,9 @@ public class BubbleController implements ConfigurationChangeListener, } }, mMainHandler); - mTaskStackListener.addListener(new TaskStackListenerCallback() { - @Override - public void onTaskMovedToFront(int taskId) { - mMainExecutor.execute(() -> { - int expandedId = INVALID_TASK_ID; - if (mStackView != null && mStackView.getExpandedBubble() != null - && isStackExpanded() - && !mStackView.isExpansionAnimating() - && !mStackView.isSwitchAnimating()) { - expandedId = mStackView.getExpandedBubble().getTaskId(); - } - if (expandedId != INVALID_TASK_ID && expandedId != taskId) { - mBubbleData.setExpanded(false); - } - }); - } + mTransitions.registerObserver(new BubblesTransitionObserver(this, mBubbleData)); + mTaskStackListener.addListener(new TaskStackListenerCallback() { @Override public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { @@ -518,11 +508,14 @@ public class BubbleController implements ConfigurationChangeListener, /** * Sets a listener to be notified of bubble updates. This is used by launcher so that * it may render bubbles in itself. Only one listener is supported. + * + * <p>If bubble bar is supported, bubble views will be updated to switch to bar mode. */ public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) { - if (isShowingAsBubbleBar()) { - // Only set the listener if bubble bar is showing. + if (canShowAsBubbleBar() && listener != null) { + // Only set the listener if we can show the bubble bar. mBubbleStateListener = listener; + setUpBubbleViewsForMode(); sendInitialListenerUpdate(); } else { mBubbleStateListener = null; @@ -531,9 +524,15 @@ public class BubbleController implements ConfigurationChangeListener, /** * Unregisters the {@link Bubbles.BubbleStateListener}. + * + * <p>If there's an existing listener, then we're switching back to stack mode and bubble views + * will be updated accordingly. */ public void unregisterBubbleStateListener() { - mBubbleStateListener = null; + if (mBubbleStateListener != null) { + mBubbleStateListener = null; + setUpBubbleViewsForMode(); + } } /** @@ -645,8 +644,12 @@ public class BubbleController implements ConfigurationChangeListener, /** Whether bubbles are showing in the bubble bar. */ public boolean isShowingAsBubbleBar() { - // TODO(b/269670598): should also check that we're in gesture nav - return BUBBLE_BAR_ENABLED && mBubblePositioner.isLargeScreen(); + return canShowAsBubbleBar() && mBubbleStateListener != null; + } + + /** Whether the current configuration supports showing as bubble bar. */ + private boolean canShowAsBubbleBar() { + return mBubbleProperties.isBubbleBarEnabled() && mBubblePositioner.isLargeScreen(); } /** Whether this userId belongs to the current user. */ @@ -719,6 +722,7 @@ public class BubbleController implements ConfigurationChangeListener, // TODO(b/273312602): consider foldables where we do need a stack view when folded if (mLayerView == null) { mLayerView = new BubbleBarLayerView(mContext, this); + mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); } } else { if (mStackView == null) { @@ -773,12 +777,12 @@ public class BubbleController implements ConfigurationChangeListener, try { mAddedToWindowManager = true; registerBroadcastReceiver(); - mBubbleData.getOverflow().initialize(this); + mBubbleData.getOverflow().initialize(this, isShowingAsBubbleBar()); // (TODO: b/273314541) some duplication in the inset listener if (isShowingAsBubbleBar()) { mWindowManager.addView(mLayerView, mWmLayoutParams); mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> { - if (!windowInsets.equals(mWindowInsets)) { + if (!windowInsets.equals(mWindowInsets) && mLayerView != null) { mWindowInsets = windowInsets; mBubblePositioner.update(); mLayerView.onDisplaySizeChanged(); @@ -788,7 +792,7 @@ public class BubbleController implements ConfigurationChangeListener, } else { mWindowManager.addView(mStackView, mWmLayoutParams); mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> { - if (!windowInsets.equals(mWindowInsets)) { + if (!windowInsets.equals(mWindowInsets) && mStackView != null) { mWindowInsets = windowInsets; mBubblePositioner.update(); mStackView.onDisplaySizeChanged(); @@ -810,13 +814,17 @@ public class BubbleController implements ConfigurationChangeListener, * @param interceptBack whether back should be intercepted or not. */ void updateWindowFlagsForBackpress(boolean interceptBack) { - if (mStackView != null && mAddedToWindowManager) { + if (mAddedToWindowManager) { mWmLayoutParams.flags = interceptBack ? 0 : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - mWindowManager.updateViewLayout(mStackView, mWmLayoutParams); + if (mStackView != null) { + mWindowManager.updateViewLayout(mStackView, mWmLayoutParams); + } else if (mLayerView != null) { + mWindowManager.updateViewLayout(mLayerView, mWmLayoutParams); + } } } @@ -867,8 +875,10 @@ public class BubbleController implements ConfigurationChangeListener, String action = intent.getAction(); String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); - if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) - && SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason)) + boolean validReasonToCollapse = SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason) + || SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason) + || SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason); + if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) && validReasonToCollapse) || Intent.ACTION_SCREEN_OFF.equals(action)) { mMainExecutor.execute(() -> collapseStack()); } @@ -993,9 +1003,7 @@ public class BubbleController implements ConfigurationChangeListener, } private void onNotificationPanelExpandedChanged(boolean expanded) { - if (DEBUG_BUBBLE_GESTURE) { - Log.d(TAG, "onNotificationPanelExpandedChanged: expanded=" + expanded); - } + ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded); if (mStackView != null && mStackView.isExpanded()) { if (expanded) { mStackView.stopMonitoringSwipeUpGesture(); @@ -1042,6 +1050,29 @@ public class BubbleController implements ConfigurationChangeListener, mBubbleData.setExpanded(false /* expanded */); } + /** + * Update expanded state when a single bubble is dragged in Launcher. + * Will be called only when bubble bar is expanded. + * @param bubbleKey key of the bubble to collapse/expand + * @param isBeingDragged whether the bubble is being dragged + */ + public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) { + if (mBubbleData.getSelectedBubble() != null + && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) { + // Should collapse/expand only if equals to selected bubble. + mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged); + } + } + + /** + * Show bubble bar user education relative to the reference position. + * @param position the reference position in Screen coordinates. + */ + public void showUserEducation(Point position) { + if (mLayerView == null) return; + mLayerView.showUserEducation(position); + } + @VisibleForTesting public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) { boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key) @@ -1065,9 +1096,19 @@ public class BubbleController implements ConfigurationChangeListener, * Expands and selects the provided bubble as long as it already exists in the stack or the * overflow. * - * This is used by external callers (launcher). + * <p>This is used by external callers (launcher). */ - public void expandStackAndSelectBubbleFromLauncher(String key) { + @VisibleForTesting + public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarOffsetX, + int bubbleBarOffsetY) { + mBubblePositioner.setBubbleBarPosition(bubbleBarOffsetX, bubbleBarOffsetY); + + if (BubbleOverflow.KEY.equals(key)) { + mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow()); + mLayerView.showExpandedView(mBubbleData.getOverflow()); + return; + } + Bubble b = mBubbleData.getAnyBubbleWithkey(key); if (b == null) { return; @@ -1084,6 +1125,16 @@ public class BubbleController implements ConfigurationChangeListener, } /** + * Expands the stack if the selected bubble is present. This is currently used when user + * education view is clicked to expand the selected bubble. + */ + public void expandStackWithSelectedBubble() { + if (mBubbleData.getSelectedBubble() != null) { + mBubbleData.setExpanded(true); + } + } + + /** * Expands and selects the provided bubble as long as it already exists in the stack or the * overflow. This is currently used when opening a bubble via clicking on a conversation widget. */ @@ -1220,6 +1271,13 @@ public class BubbleController implements ConfigurationChangeListener, } /** + * Dismiss bubble if it exists and remove it from the stack + */ + public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) { + mBubbleData.dismissBubbleWithKey(bubble.getKey(), reason); + } + + /** * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot * can be access via the supplied {@link SynchronousScreenCaptureListener#getBuffer()} * asynchronously. @@ -1259,7 +1317,9 @@ public class BubbleController implements ConfigurationChangeListener, return; } mOverflowDataLoadNeeded = false; - mDataRepository.loadBubbles(mCurrentUserId, (bubbles) -> { + List<UserInfo> users = mUserManager.getAliveUsers(); + List<Integer> userIds = users.stream().map(userInfo -> userInfo.id).toList(); + mDataRepository.loadBubbles(mCurrentUserId, userIds, (bubbles) -> { bubbles.forEach(bubble -> { if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) { // if the bubble is already active, there's no need to push it to overflow @@ -1278,6 +1338,50 @@ public class BubbleController implements ConfigurationChangeListener, }); } + void setUpBubbleViewsForMode() { + mBubbleViewCallback = isShowingAsBubbleBar() + ? mBubbleBarViewCallback + : mBubbleStackViewCallback; + + // reset the overflow so that it can be re-added later if needed. + if (mStackView != null) { + mStackView.resetOverflowView(); + mStackView.removeAllViews(); + } + // cleanup existing bubble views so they can be recreated later if needed. + mBubbleData.getBubbles().forEach(Bubble::cleanupViews); + + // remove the current bubble container from window manager, null it out, and create a new + // container based on the current mode. + removeFromWindowManagerMaybe(); + mLayerView = null; + mStackView = null; + ensureBubbleViewsAndWindowCreated(); + + // inflate bubble views + BubbleViewInfoTask.Callback callback = null; + if (!isShowingAsBubbleBar()) { + callback = b -> { + if (mStackView != null) { + mStackView.addBubble(b); + mStackView.setSelectedBubble(b); + } else { + Log.w(TAG, "Tried to add a bubble to the stack but the stack is null"); + } + }; + } + for (int i = mBubbleData.getBubbles().size() - 1; i >= 0; i--) { + Bubble bubble = mBubbleData.getBubbles().get(i); + bubble.inflate(callback, + mContext, + this, + mStackView, + mLayerView, + mBubbleIconFactory, + false /* skipInflation */); + } + } + /** * Adds or updates a bubble associated with the provided notification entry. * @@ -1364,6 +1468,17 @@ public class BubbleController implements ConfigurationChangeListener, } } + /** + * Removes all the bubbles. + * <p> + * Must be called from the main thread. + */ + @VisibleForTesting + @MainThread + public void removeAllBubbles(@Bubbles.DismissReason int reason) { + mBubbleData.dismissAll(reason); + } + private void onEntryAdded(BubbleEntry entry) { if (canLaunchInTaskView(mContext, entry)) { updateBubble(entry); @@ -1635,7 +1750,8 @@ public class BubbleController implements ConfigurationChangeListener, + " expandedChanged=" + update.expandedChanged + " selectionChanged=" + update.selectionChanged + " suppressed=" + (update.suppressedBubble != null) - + " unsuppressed=" + (update.unsuppressedBubble != null)); + + " unsuppressed=" + (update.unsuppressedBubble != null) + + " shouldShowEducation=" + update.shouldShowEducation); } ensureBubbleViewsAndWindowCreated(); @@ -1738,7 +1854,7 @@ public class BubbleController implements ConfigurationChangeListener, // Update the cached state for queries from SysUI mImpl.mCachedState.update(update); - if (isShowingAsBubbleBar() && mBubbleStateListener != null) { + if (isShowingAsBubbleBar()) { BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate(); // Some updates aren't relevant to the bubble bar so check first. if (bubbleBarUpdate.anythingChanged()) { @@ -1846,7 +1962,7 @@ public class BubbleController implements ConfigurationChangeListener, if (mStackView != null) { mStackView.setVisibility(VISIBLE); } - if (mLayerView != null && isStackExpanded()) { + if (mLayerView != null) { mLayerView.setVisibility(VISIBLE); } } @@ -1859,11 +1975,27 @@ public class BubbleController implements ConfigurationChangeListener, } } + /** + * Returns whether the stack is animating or not. + */ + public boolean isStackAnimating() { + return mStackView != null + && (mStackView.isExpansionAnimating() + || mStackView.isSwitchAnimating()); + } + @VisibleForTesting + @Nullable public BubbleStackView getStackView() { return mStackView; } + @VisibleForTesting + @Nullable + public BubbleBarLayerView getLayerView() { + return mLayerView; + } + /** * Check if notification panel is in an expanded state. * Makes a call to System UI process and delivers the result via {@code callback} on the @@ -1880,13 +2012,20 @@ public class BubbleController implements ConfigurationChangeListener, * Description of current bubble state. */ private void dump(PrintWriter pw, String prefix) { - pw.println("BubbleController state:"); + pw.print(prefix); pw.println("BubbleController state:"); + pw.print(prefix); pw.println(" currentUserId= " + mCurrentUserId); + pw.print(prefix); pw.println(" isStatusBarShade= " + mIsStatusBarShade); + pw.print(prefix); pw.println(" isShowingAsBubbleBar= " + isShowingAsBubbleBar()); + pw.println(); + mBubbleData.dump(pw); pw.println(); + if (mStackView != null) { mStackView.dump(pw); } pw.println(); + mImpl.mCachedState.dump(pw); } @@ -2002,27 +2141,30 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void registerBubbleListener(IBubblesListener listener) { - mMainExecutor.execute(() -> { - mListener.register(listener); - }); + mMainExecutor.execute(() -> mListener.register(listener)); } @Override public void unregisterBubbleListener(IBubblesListener listener) { - mMainExecutor.execute(() -> mListener.unregister()); + mMainExecutor.execute(mListener::unregister); } @Override - public void showBubble(String key, boolean onLauncherHome) { - mMainExecutor.execute(() -> { - mBubblePositioner.setShowingInBubbleBar(onLauncherHome); - mController.expandStackAndSelectBubbleFromLauncher(key); - }); + public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) { + mMainExecutor.execute( + () -> mController.expandStackAndSelectBubbleFromLauncher( + key, bubbleBarOffsetX, bubbleBarOffsetY)); } @Override - public void removeBubble(String key, int reason) { - // TODO (b/271466616) allow removals from launcher + public void removeBubble(String key) { + mMainExecutor.execute( + () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE)); + } + + @Override + public void removeAllBubbles() { + mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE)); } @Override @@ -2031,8 +2173,14 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void onTaskbarStateChanged(int newState) { - // TODO (b/269670598) + public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) { + mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged)); + } + + @Override + public void showUserEducation(int positionX, int positionY) { + mMainExecutor.execute(() -> + mController.showUserEducation(new Point(positionX, positionY))); } } @@ -2132,8 +2280,7 @@ public class BubbleController implements ConfigurationChangeListener, pw.println("mIsStackExpanded: " + mIsStackExpanded); pw.println("mSelectedBubbleKey: " + mSelectedBubbleKey); - pw.print("mSuppressedBubbleKeys: "); - pw.println(mSuppressedBubbleKeys.size()); + pw.println("mSuppressedBubbleKeys: " + mSuppressedBubbleKeys.size()); for (String key : mSuppressedBubbleKeys) { pw.println(" suppressing: " + key); } @@ -2144,7 +2291,7 @@ public class BubbleController implements ConfigurationChangeListener, pw.println(" suppressing: " + key); } - pw.print("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values()); + pw.println("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values()); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index cc8f50e09fcb..595a4afbfc86 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -77,6 +77,7 @@ public class BubbleData { boolean orderChanged; boolean suppressedSummaryChanged; boolean expanded; + boolean shouldShowEducation; @Nullable BubbleViewProvider selectedBubble; @Nullable Bubble addedBubble; @Nullable Bubble updatedBubble; @@ -126,6 +127,7 @@ public class BubbleData { bubbleBarUpdate.expandedChanged = expandedChanged; bubbleBarUpdate.expanded = expanded; + bubbleBarUpdate.shouldShowEducation = shouldShowEducation; if (selectionChanged) { bubbleBarUpdate.selectedBubbleKey = selectedBubble != null ? selectedBubble.getKey() @@ -165,6 +167,7 @@ public class BubbleData { */ BubbleBarUpdate getInitialState() { BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate(); + bubbleBarUpdate.shouldShowEducation = shouldShowEducation; for (int i = 0; i < bubbles.size(); i++) { bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble()); } @@ -187,6 +190,7 @@ public class BubbleData { private final Context mContext; private final BubblePositioner mPositioner; + private final BubbleEducationController mEducationController; private final Executor mMainExecutor; /** Bubbles that are actively in the stack. */ private final List<Bubble> mBubbles; @@ -233,10 +237,11 @@ public class BubbleData { private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>(); public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner, - Executor mainExecutor) { + BubbleEducationController educationController, Executor mainExecutor) { mContext = context; mLogger = bubbleLogger; mPositioner = positioner; + mEducationController = educationController; mMainExecutor = mainExecutor; mOverflow = new BubbleOverflow(context, positioner); mBubbles = new ArrayList<>(); @@ -447,6 +452,7 @@ public class BubbleData { if (bubble.shouldAutoExpand()) { bubble.setShouldAutoExpand(false); setSelectedBubbleInternal(bubble); + if (!mExpanded) { setExpandedInternal(true); } @@ -877,6 +883,9 @@ public class BubbleData { private void dispatchPendingChanges() { if (mListener != null && mStateChange.anythingChanged()) { + mStateChange.shouldShowEducation = mSelectedBubble != null + && mEducationController.shouldShowStackEducation(mSelectedBubble) + && !mExpanded; mListener.applyUpdate(mStateChange); } mStateChange = new Update(mBubbles, mOverflowBubbles); @@ -1231,29 +1240,30 @@ public class BubbleData { * Description of current bubble data state. */ public void dump(PrintWriter pw) { - pw.print("selected: "); + pw.println("BubbleData state:"); + pw.print(" selected: "); pw.println(mSelectedBubble != null ? mSelectedBubble.getKey() : "null"); - pw.print("expanded: "); + pw.print(" expanded: "); pw.println(mExpanded); - pw.print("stack bubble count: "); + pw.print("Stack bubble count: "); pw.println(mBubbles.size()); for (Bubble bubble : mBubbles) { bubble.dump(pw); } - pw.print("overflow bubble count: "); + pw.print("Overflow bubble count: "); pw.println(mOverflowBubbles.size()); for (Bubble bubble : mOverflowBubbles) { bubble.dump(pw); } - pw.print("summaryKeys: "); + pw.print("SummaryKeys: "); pw.println(mSuppressedGroupKeys.size()); for (String key : mSuppressedGroupKeys.keySet()) { - pw.println(" suppressing: " + key); + pw.println(" suppressing: " + key); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt index e37c785f15f5..df12999afc9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.bubbles import android.annotation.SuppressLint import android.annotation.UserIdInt -import android.content.Context import android.content.pm.LauncherApps import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC @@ -25,6 +24,8 @@ import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LA import android.content.pm.UserInfo import android.os.UserHandle import android.util.Log +import android.util.SparseArray +import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener import com.android.wm.shell.bubbles.storage.BubbleEntity import com.android.wm.shell.bubbles.storage.BubblePersistentRepository @@ -33,19 +34,19 @@ import com.android.wm.shell.common.ShellExecutor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch import kotlinx.coroutines.yield -internal class BubbleDataRepository( - context: Context, +class BubbleDataRepository( private val launcherApps: LauncherApps, - private val mainExecutor: ShellExecutor + private val mainExecutor: ShellExecutor, + private val persistentRepository: BubblePersistentRepository, ) { private val volatileRepository = BubbleVolatileRepository(launcherApps) - private val persistentRepository = BubblePersistentRepository(context) - private val ioScope = CoroutineScope(Dispatchers.IO) + private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private var job: Job? = null // For use in Bubble construction. @@ -98,6 +99,44 @@ internal class BubbleDataRepository( if (volatileRepository.sanitizeBubbles(userIds)) persistToDisk() } + /** + * Removes all entities that don't have a user in the activeUsers list, if any entities were + * removed it persists the new list to disk. + */ + @VisibleForTesting + fun filterForActiveUsersAndPersist( + activeUsers: List<Int>, + entitiesByUser: SparseArray<List<BubbleEntity>> + ): SparseArray<List<BubbleEntity>> { + val validEntitiesByUser = SparseArray<List<BubbleEntity>>() + var entitiesChanged = false + for (i in 0 until entitiesByUser.size()) { + val parentUserId = entitiesByUser.keyAt(i) + if (activeUsers.contains(parentUserId)) { + val validEntities = mutableListOf<BubbleEntity>() + // Check if each of the bubbles in the top-level user still has a valid user + // as it could belong to a profile and have a different id from the parent. + for (entity in entitiesByUser.get(parentUserId)) { + if (activeUsers.contains(entity.userId)) { + validEntities.add(entity) + } else { + entitiesChanged = true + } + } + if (validEntities.isNotEmpty()) { + validEntitiesByUser.put(parentUserId, validEntities) + } + } else { + entitiesChanged = true + } + } + if (entitiesChanged) { + persistToDisk(validEntitiesByUser) + return validEntitiesByUser + } + return entitiesByUser + } + private fun transform(bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> BubbleEntity( @@ -129,15 +168,18 @@ internal class BubbleDataRepository( * Job C resumes and reaches yield() and is then cancelled * Job D resumes and performs another blocking I/O */ - private fun persistToDisk() { + @VisibleForTesting + fun persistToDisk( + entitiesByUser: SparseArray<List<BubbleEntity>> = volatileRepository.bubbles + ) { val prev = job - job = ioScope.launch { + job = coroutineScope.launch { // if there was an ongoing disk I/O operation, they can be cancelled prev?.cancelAndJoin() // check for cancellation before disk I/O yield() // save to disk - persistentRepository.persistsToDisk(volatileRepository.bubbles) + persistentRepository.persistsToDisk(entitiesByUser) } } @@ -148,7 +190,11 @@ internal class BubbleDataRepository( * bubbles. */ @SuppressLint("WrongConstant") - fun loadBubbles(userId: Int, cb: (List<Bubble>) -> Unit) = ioScope.launch { + fun loadBubbles( + userId: Int, + currentUsers: List<Int>, + cb: (List<Bubble>) -> Unit + ) = coroutineScope.launch { /** * Load BubbleEntity from disk. * e.g. @@ -159,7 +205,12 @@ internal class BubbleDataRepository( * ] */ val entitiesByUser = persistentRepository.readFromDisk() - val entities = entitiesByUser.get(userId) ?: return@launch + + // Before doing anything, validate that the entities we loaded are valid & have an existing + // user. + val validEntitiesByUser = filterForActiveUsersAndPersist(currentUsers, entitiesByUser) + + val entities = validEntitiesByUser.get(userId) ?: return@launch volatileRepository.addBubbles(userId, entities) /** * Extract userId/packageName from these entities. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java index dce6b56261ff..f56b1712c5c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java @@ -44,15 +44,19 @@ public class BubbleDebugConfig { static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false; static final boolean DEBUG_EXPERIMENTS = true; static final boolean DEBUG_OVERFLOW = false; - static final boolean DEBUG_USER_EDUCATION = false; + public static final boolean DEBUG_USER_EDUCATION = false; static final boolean DEBUG_POSITIONER = false; public static final boolean DEBUG_COLLAPSE_ANIMATOR = false; - static final boolean DEBUG_BUBBLE_GESTURE = false; public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false; private static final boolean FORCE_SHOW_USER_EDUCATION = false; private static final String FORCE_SHOW_USER_EDUCATION_SETTING = "force_show_bubbles_user_education"; + /** + * When set to true, bubbles user education flow never shows up. + */ + private static final String FORCE_HIDE_USER_EDUCATION_SETTING = + "force_hide_bubbles_user_education"; /** * @return whether we should force show user education for bubbles. Used for debugging & demos. @@ -63,22 +67,35 @@ public class BubbleDebugConfig { return FORCE_SHOW_USER_EDUCATION || forceShow; } + /** + * @return whether we should never show user education for bubbles. Used in tests. + */ + static boolean neverShowUserEducation(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + FORCE_HIDE_USER_EDUCATION_SETTING, 0) != 0; + } + static String formatBubblesString(List<Bubble> bubbles, BubbleViewProvider selected) { StringBuilder sb = new StringBuilder(); - for (Bubble bubble : bubbles) { + for (int i = 0; i < bubbles.size(); i++) { + Bubble bubble = bubbles.get(i); if (bubble == null) { - sb.append(" <null> !!!!!\n"); + sb.append(" <null> !!!!!"); } else { boolean isSelected = (selected != null - && selected.getKey() != BubbleOverflow.KEY + && !BubbleOverflow.KEY.equals(selected.getKey()) && bubble == selected); String arrow = isSelected ? "=>" : " "; - sb.append(String.format("%s Bubble{act=%12d, showInShade=%d, key=%s}\n", + + sb.append(String.format("%s Bubble{act=%12d, showInShade=%d, key=%s}", arrow, bubble.getLastActivity(), (bubble.showInShade() ? 1 : 0), bubble.getKey())); } + if (i != bubbles.size() - 1) { + sb.append("\n"); + } } return sb.toString(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt new file mode 100644 index 000000000000..e57f02c71e44 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles + +import android.content.Context +import android.util.Log +import androidx.core.content.edit +import com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION +import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES +import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME + +/** Manages bubble education flags. Provides convenience methods to check the education state */ +class BubbleEducationController(private val context: Context) { + private val prefs = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE) + + /** Whether the user has seen the stack education */ + @get:JvmName(name = "hasSeenStackEducation") + var hasSeenStackEducation: Boolean + get() = prefs.getBoolean(PREF_STACK_EDUCATION, false) + set(value) = prefs.edit { putBoolean(PREF_STACK_EDUCATION, value) } + + /** Whether the user has seen the expanded view "manage" menu education */ + @get:JvmName(name = "hasSeenManageEducation") + var hasSeenManageEducation: Boolean + get() = prefs.getBoolean(PREF_MANAGED_EDUCATION, false) + set(value) = prefs.edit { putBoolean(PREF_MANAGED_EDUCATION, value) } + + /** Whether education view should show for the collapsed stack. */ + fun shouldShowStackEducation(bubble: BubbleViewProvider?): Boolean { + val shouldShow = bubble != null && + bubble.isConversationBubble && // show education for conversation bubbles only + (!hasSeenStackEducation || BubbleDebugConfig.forceShowUserEducation(context)) + logDebug("Show stack edu: $shouldShow") + return shouldShow + } + + /** Whether the educational view should show for the expanded view "manage" menu. */ + fun shouldShowManageEducation(bubble: BubbleViewProvider?): Boolean { + val shouldShow = bubble != null && + bubble.isConversationBubble && // show education for conversation bubbles only + (!hasSeenManageEducation || BubbleDebugConfig.forceShowUserEducation(context)) + logDebug("Show manage edu: $shouldShow") + return shouldShow + } + + private fun logDebug(message: String) { + if (DEBUG_USER_EDUCATION) { + Log.d(TAG, message) + } + } + + companion object { + private val TAG = if (TAG_WITH_CLASS_NAME) "BubbleEducationController" else TAG_BUBBLES + const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding" + const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding" + } +} + +/** Convenience extension method to check if the bubble is a conversation bubble */ +private val BubbleViewProvider.isConversationBubble: Boolean + get() = if (this is Bubble) isConversation else false 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 e1a3f3a1ac5d..37bcf1ddeac5 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 @@ -312,9 +312,13 @@ public class BubbleExpandedView extends LinearLayout { + " bubble=" + getBubbleKey()); } if (mBubble != null) { - // Must post because this is called from a binder thread. - post(() -> mController.removeBubble( - mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED)); + mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED); + } + if (mTaskView != null) { + // Release the surface + mTaskView.release(); + removeView(mTaskView); + mTaskView = null; } } @@ -948,9 +952,9 @@ public class BubbleExpandedView extends LinearLayout { mTaskView.onLocationChanged(); } if (mIsOverflow) { - post(() -> { - mOverflowView.show(); - }); + // post this to the looper so that the view has a chance to be laid out before it can + // calculate row and column sizes correctly. + post(() -> mOverflowView.show()); } } @@ -1058,8 +1062,10 @@ public class BubbleExpandedView extends LinearLayout { } /** - * Cleans up anything related to the task and {@code TaskView}. If this view should be reused - * after this method is called, then + * Cleans up anything related to the task. The TaskView itself is released after the task + * has been removed. + * + * If this view should be reused after this method is called, then * {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first. */ public void cleanUpExpandedState() { @@ -1081,19 +1087,16 @@ public class BubbleExpandedView extends LinearLayout { } } if (mTaskView != null) { - // Release the surface & other task view related things - mTaskView.release(); - removeView(mTaskView); - mTaskView = null; + mTaskView.setVisibility(GONE); } } /** * Description of current expanded view state. */ - public void dump(@NonNull PrintWriter pw) { - pw.print("BubbleExpandedView"); - pw.print(" taskId: "); pw.println(mTaskId); - pw.print(" stackView: "); pw.println(mStackView); + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + pw.print(prefix); pw.println("BubbleExpandedView:"); + pw.print(prefix); pw.print(" taskId: "); pw.println(mTaskId); + pw.print(prefix); pw.print(" stackView: "); pw.println(mStackView); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index df7f5b4f0150..df19757203eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -44,6 +44,7 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl private val inflater: LayoutInflater = LayoutInflater.from(context) private var expandedView: BubbleExpandedView? + private var bubbleBarExpandedView: BubbleBarExpandedView? = null private var overflowBtn: BadgedImageView? init { @@ -53,19 +54,26 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl } /** Call before use and again if cleanUpExpandedState was called. */ - fun initialize(controller: BubbleController) { - createExpandedView() - getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */) + fun initialize(controller: BubbleController, forBubbleBar: Boolean) { + if (forBubbleBar) { + createBubbleBarExpandedView().initialize(controller, true /* isOverflow */) + } else { + createExpandedView() + .initialize(controller, controller.stackView, true /* isOverflow */) + } } fun cleanUpExpandedState() { expandedView?.cleanUpExpandedState() expandedView = null + bubbleBarExpandedView?.cleanUpExpandedState() + bubbleBarExpandedView = null } fun update() { updateResources() getExpandedView()?.applyThemeAttrs() + getBubbleBarExpandedView()?.applyThemeAttrs() // Apply inset and new style to fresh icon drawable. getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button) updateBtnTheme() @@ -151,26 +159,39 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl overflowBtn?.updateDotVisibility(true /* animate */) } - fun createExpandedView(): BubbleExpandedView? { - expandedView = + /** Creates the expanded view for bubbles showing in the stack view. */ + private fun createExpandedView(): BubbleExpandedView { + val view = inflater.inflate( R.layout.bubble_expanded_view, null /* root */, false /* attachToRoot */ ) as BubbleExpandedView - expandedView?.applyThemeAttrs() + view.applyThemeAttrs() + expandedView = view updateResources() - return expandedView + return view } override fun getExpandedView(): BubbleExpandedView? { return expandedView } - override fun getBubbleBarExpandedView(): BubbleBarExpandedView? { - return null + /** Creates the expanded view for bubbles showing in the bubble bar. */ + private fun createBubbleBarExpandedView(): BubbleBarExpandedView { + val view = + inflater.inflate( + R.layout.bubble_bar_expanded_view, + null, /* root */ + false /* attachToRoot*/ + ) as BubbleBarExpandedView + view.applyThemeAttrs() + bubbleBarExpandedView = view + return view } + override fun getBubbleBarExpandedView(): BubbleBarExpandedView? = bubbleBarExpandedView + override fun getDotColor(): Int { return dotColor } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt new file mode 100644 index 000000000000..bdb09e11d5ad --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.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.bubbles + +import android.graphics.Color +import com.android.wm.shell.R +import com.android.wm.shell.common.bubbles.BubblePopupDrawable +import com.android.wm.shell.common.bubbles.BubblePopupView + +/** + * A convenience method to setup the [BubblePopupView] with the correct config using local resources + */ +fun BubblePopupView.setup() { + val attrs = + context.obtainStyledAttributes( + intArrayOf( + com.android.internal.R.attr.materialColorSurface, + android.R.attr.dialogCornerRadius + ) + ) + + val res = context.resources + val config = + BubblePopupDrawable.Config( + color = attrs.getColor(0, Color.WHITE), + cornerRadius = attrs.getDimension(1, 0f), + contentPadding = res.getDimensionPixelSize(R.dimen.bubble_popup_padding), + arrowWidth = res.getDimension(R.dimen.bubble_popup_arrow_width), + arrowHeight = res.getDimension(R.dimen.bubble_popup_arrow_height), + arrowRadius = res.getDimension(R.dimen.bubble_popup_arrow_corner_radius) + ) + attrs.recycle() + setupBackground(config) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index d101b0c4d7e8..ea7053d8ee49 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; +import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; @@ -102,10 +103,7 @@ public class BubblePositioner { private int[] mPaddings = new int[4]; private boolean mShowingInBubbleBar; - private boolean mBubblesOnHome; - private int mBubbleBarSize; - private int mBubbleBarHomeAdjustment; - private final PointF mBubbleBarPosition = new PointF(); + private final Point mBubbleBarPosition = new Point(); public BubblePositioner(Context context, WindowManager windowManager) { mContext = context; @@ -166,11 +164,9 @@ public class BubblePositioner { mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing); mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered); mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); - mBubbleBarHomeAdjustment = mExpandedViewPadding / 2; mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); mBubbleOffscreenAmount = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); - mBubbleBarSize = res.getDimensionPixelSize(R.dimen.bubblebar_size); if (mShowingInBubbleBar) { mExpandedViewLargeScreenWidth = isLandscape() @@ -657,22 +653,58 @@ public class BubblePositioner { } /** - * @return the stack position to use if we don't have a saved location or if user education - * is being shown. + * Returns whether the {@link #getRestingPosition()} is equal to the default start position + * initialized for bubbles, if {@code true} this means the user hasn't moved the bubble + * from the initial start position (or they haven't received a bubble yet). + */ + public boolean hasUserModifiedDefaultPosition() { + PointF defaultStart = getDefaultStartPosition(); + return mRestingStackPosition != null + && !mRestingStackPosition.equals(defaultStart); + } + + /** + * Returns the stack position to use if we don't have a saved location or if user education + * is being shown, for a normal bubble. */ public PointF getDefaultStartPosition() { - // Start on the left if we're in LTR, right otherwise. - final boolean startOnLeft = - mContext.getResources().getConfiguration().getLayoutDirection() - != LAYOUT_DIRECTION_RTL; - final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset( - R.dimen.bubble_stack_starting_offset_y); - // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge - return new BubbleStackView.RelativeStackPosition( - startOnLeft, - startingVerticalOffset / mPositionRect.height()) - .getAbsolutePositionInRegion(getAllowableStackPositionRegion( - 1 /* default starts with 1 bubble */)); + return getDefaultStartPosition(false /* isAppBubble */); + } + + /** + * The stack position to use if we don't have a saved location or if user education + * is being shown. + * + * @param isAppBubble whether this start position is for an app bubble or not. + */ + public PointF getDefaultStartPosition(boolean isAppBubble) { + final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection(); + // Normal bubbles start on the left if we're in LTR, right otherwise. + // TODO (b/294284894): update language around "app bubble" here + // App bubbles start on the right in RTL, left otherwise. + final boolean startOnLeft = isAppBubble + ? layoutDirection == LAYOUT_DIRECTION_RTL + : layoutDirection != LAYOUT_DIRECTION_RTL; + final RectF allowableStackPositionRegion = getAllowableStackPositionRegion( + 1 /* default starts with 1 bubble */); + if (isLargeScreen()) { + // We want the stack to be visually centered on the edge, so we need to base it + // of a rect that includes insets. + final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f); + final float offset = desiredY / mScreenRect.height(); + return new BubbleStackView.RelativeStackPosition( + startOnLeft, + offset) + .getAbsolutePositionInRegion(allowableStackPositionRegion); + } else { + final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset( + R.dimen.bubble_stack_starting_offset_y); + // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge + return new BubbleStackView.RelativeStackPosition( + startOnLeft, + startingVerticalOffset / mPositionRect.height()) + .getAbsolutePositionInRegion(allowableStackPositionRegion); + } } /** @@ -723,27 +755,36 @@ public class BubblePositioner { } /** - * Sets whether bubbles are showing on launcher home, in which case positions are different. + * Sets the position of the bubble bar in screen coordinates. + * + * @param offsetX the offset of the bubble bar from the edge of the screen on the X axis + * @param offsetY the offset of the bubble bar from the edge of the screen on the Y axis */ - public void setBubblesOnHome(boolean bubblesOnHome) { - mBubblesOnHome = bubblesOnHome; + public void setBubbleBarPosition(int offsetX, int offsetY) { + mBubbleBarPosition.set( + getAvailableRect().width() - offsetX, + getAvailableRect().height() + mInsets.top + mInsets.bottom - offsetY); } /** * How wide the expanded view should be when showing from the bubble bar. */ - public int getExpandedViewWidthForBubbleBar() { - return mExpandedViewLargeScreenWidth; + public int getExpandedViewWidthForBubbleBar(boolean isOverflow) { + return isOverflow ? mOverflowWidth : mExpandedViewLargeScreenWidth; } /** * How tall the expanded view should be when showing from the bubble bar. */ - public int getExpandedViewHeightForBubbleBar() { - return getAvailableRect().height() - - mBubbleBarSize - - mExpandedViewPadding * 2 - - getBubbleBarHomeAdjustment(); + public int getExpandedViewHeightForBubbleBar(boolean isOverflow) { + return isOverflow + ? mOverflowHeight + : getExpandedViewBottomForBubbleBar() - mInsets.top - mExpandedViewPadding; + } + + /** The bottom position of the expanded view when showing above the bubble bar. */ + public int getExpandedViewBottomForBubbleBar() { + return mBubbleBarPosition.y - mExpandedViewPadding; } /** @@ -756,19 +797,7 @@ public class BubblePositioner { /** * Returns the on screen co-ordinates of the bubble bar. */ - public PointF getBubbleBarPosition() { - mBubbleBarPosition.set(getAvailableRect().width() - mBubbleBarSize, - getAvailableRect().height() - mBubbleBarSize - - mExpandedViewPadding - getBubbleBarHomeAdjustment()); + public Point getBubbleBarPosition() { return mBubbleBarPosition; } - - /** - * When bubbles are shown on launcher home, there's an extra bit of padding that needs to - * be applied between the expanded view and the bubble bar. This returns the adjustment value - * if bubbles are showing on home. - */ - private int getBubbleBarHomeAdjustment() { - return mBubblesOnHome ? mBubbleBarHomeAdjustment : 0; - } } 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 68fea41e134e..c124b532b89d 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 @@ -21,11 +21,12 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.wm.shell.animation.Interpolators.ALPHA_IN; import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; +import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -46,7 +47,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; -import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; import android.view.Choreographer; @@ -75,6 +75,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; @@ -88,6 +89,8 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout; import com.android.wm.shell.bubbles.animation.StackAnimationController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.bubbles.DismissView; +import com.android.wm.shell.common.bubbles.RelativeTouchListener; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import java.io.PrintWriter; @@ -105,10 +108,6 @@ import java.util.stream.Collectors; */ public class BubbleStackView extends FrameLayout implements ViewTreeObserver.OnComputeInternalInsetsListener { - - public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE = - SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true); - private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES; /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */ @@ -133,8 +132,6 @@ public class BubbleStackView extends FrameLayout private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150; - private static final float SCRIM_ALPHA = 0.6f; - /** Minimum alpha value for scrim when alpha is being changed via drag */ private static final float MIN_SCRIM_ALPHA_FOR_DRAG = 0.2f; @@ -307,7 +304,7 @@ public class BubbleStackView extends FrameLayout String bubblesOnScreen = BubbleDebugConfig.formatBubblesString( getBubblesOnScreen(), getExpandedBubble()); - pw.print(" bubbles on screen: "); pw.println(bubblesOnScreen); + pw.println(" bubbles on screen: "); pw.println(bubblesOnScreen); pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress); pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing()); pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating); @@ -315,7 +312,8 @@ public class BubbleStackView extends FrameLayout pw.print(" expandedContainerAlpha: "); pw.println(mExpandedViewContainer.getAlpha()); pw.print(" expandedContainerMatrix: "); pw.println(mExpandedViewContainer.getAnimationMatrix()); - + pw.print(" stack visibility : "); pw.println(getVisibility()); + pw.print(" temporarilyInvisible: "); pw.println(mTemporarilyInvisible); mStackAnimationController.dump(pw); mExpandedAnimationController.dump(pw); @@ -780,14 +778,15 @@ public class BubbleStackView extends FrameLayout private float getScrimAlphaForDrag(float dragAmount) { // dragAmount should be negative as we allow scroll up only if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - float alphaRange = SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG; + float alphaRange = BUBBLE_EXPANDED_SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG; int dragMax = mExpandedBubble.getExpandedView().getContentHeight(); float dragFraction = dragAmount / dragMax; - return Math.max(SCRIM_ALPHA - alphaRange * dragFraction, MIN_SCRIM_ALPHA_FOR_DRAG); + return Math.max(BUBBLE_EXPANDED_SCRIM_ALPHA - alphaRange * dragFraction, + MIN_SCRIM_ALPHA_FOR_DRAG); } - return SCRIM_ALPHA; + return BUBBLE_EXPANDED_SCRIM_ALPHA; } }; @@ -968,6 +967,8 @@ public class BubbleStackView extends FrameLayout mBubbleContainer.bringToFront(); mBubbleOverflow = mBubbleData.getOverflow(); + + resetOverflowView(); mBubbleContainer.addView(mBubbleOverflow.getIconView(), mBubbleContainer.getChildCount() /* index */, new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), @@ -1179,6 +1180,7 @@ public class BubbleStackView extends FrameLayout removeView(mDismissView); } mDismissView = new DismissView(getContext()); + DismissViewUtils.setup(mDismissView); int elevation = getResources().getDimensionPixelSize(R.dimen.bubble_elevation); addView(mDismissView); @@ -1282,6 +1284,12 @@ public class BubbleStackView extends FrameLayout if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { Log.d(TAG, "Show manage edu: " + shouldShow); } + if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) { + if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { + Log.d(TAG, "Want to show manage edu, but it is forced hidden"); + } + return false; + } return shouldShow; } @@ -1314,6 +1322,12 @@ public class BubbleStackView extends FrameLayout if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { Log.d(TAG, "Show stack edu: " + shouldShow); } + if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) { + if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { + Log.d(TAG, "Want to show stack edu, but it is forced hidden"); + } + return false; + } return shouldShow; } @@ -1494,9 +1508,6 @@ public class BubbleStackView extends FrameLayout getViewTreeObserver().removeOnPreDrawListener(mViewUpdater); getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater); getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - if (mBubbleOverflow != null) { - mBubbleOverflow.cleanUpExpandedState(); - } } @Override @@ -1764,13 +1775,26 @@ public class BubbleStackView extends FrameLayout return; } + if (firstBubble && bubble.isAppBubble() && !mPositioner.hasUserModifiedDefaultPosition()) { + // TODO (b/294284894): update language around "app bubble" here + // If it's an app bubble and we don't have a previous resting position, update the + // controllers to use the default position for the app bubble (it'd be different from + // the position initialized with the controllers originally). + PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); + mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition); + mStackAnimationController.setStackPosition(startPosition); + mExpandedAnimationController.setCollapsePoint(startPosition); + // Set the translation x so that this bubble will animate in from the same side they + // expand / collapse on. + bubble.getIconView().setTranslationX(startPosition.x); + } else if (firstBubble) { + mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); + } + mBubbleContainer.addView(bubble.getIconView(), 0, new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), mPositioner.getBubbleSize())); - if (firstBubble) { - mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); - } // Set the dot position to the opposite of the side the stack is resting on, since the stack // resting slightly off-screen would result in the dot also being off-screen. bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */); @@ -2013,15 +2037,14 @@ public class BubbleStackView extends FrameLayout }); } notifyExpansionChanged(mExpandedBubble, mIsExpanded); + announceExpandForAccessibility(mExpandedBubble, mIsExpanded); } /** * Monitor for swipe up gesture that is used to collapse expanded view */ void startMonitoringSwipeUpGesture() { - if (DEBUG_BUBBLE_GESTURE) { - Log.d(TAG, "startMonitoringSwipeUpGesture"); - } + ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture"); stopMonitoringSwipeUpGestureInternal(); if (isGestureNavEnabled()) { @@ -2031,6 +2054,34 @@ public class BubbleStackView extends FrameLayout } } + private void announceExpandForAccessibility(BubbleViewProvider bubble, boolean expanded) { + if (bubble instanceof Bubble) { + String contentDescription = getBubbleContentDescription((Bubble) bubble); + String message = getResources().getString( + expanded + ? R.string.bubble_accessibility_announce_expand + : R.string.bubble_accessibility_announce_collapse, contentDescription); + announceForAccessibility(message); + } + } + + @NonNull + private String getBubbleContentDescription(Bubble bubble) { + final String appName = bubble.getAppName(); + final String title = bubble.getTitle() != null + ? bubble.getTitle() + : getResources().getString(R.string.notification_bubble_title); + + if (appName == null || title.equals(appName)) { + // App bubble title equals the app name, so return only the title to avoid having + // content description like: `<app> from <app>`. + return title; + } else { + return getResources().getString( + R.string.bubble_content_description_single, title, appName); + } + } + private boolean isGestureNavEnabled() { return mContext.getResources().getInteger( com.android.internal.R.integer.config_navBarInteractionMode) @@ -2041,9 +2092,7 @@ public class BubbleStackView extends FrameLayout * Stop monitoring for swipe up gesture */ void stopMonitoringSwipeUpGesture() { - if (DEBUG_BUBBLE_GESTURE) { - Log.d(TAG, "stopMonitoringSwipeUpGesture"); - } + ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture"); stopMonitoringSwipeUpGestureInternal(); } @@ -2194,7 +2243,7 @@ public class BubbleStackView extends FrameLayout if (show) { mScrim.animate() .setInterpolator(ALPHA_IN) - .alpha(SCRIM_ALPHA) + .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA) .setListener(listener) .start(); } else { @@ -2923,7 +2972,7 @@ public class BubbleStackView extends FrameLayout mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show); mManageMenuScrim.animate() .setInterpolator(show ? ALPHA_IN : ALPHA_OUT) - .alpha(show ? SCRIM_ALPHA : 0f) + .alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f) .withEndAction(endAction) .start(); @@ -3411,6 +3460,19 @@ public class BubbleStackView extends FrameLayout } /** + * Removes the overflow view from the stack. This allows for re-adding it later to a new stack. + */ + void resetOverflowView() { + BadgedImageView overflowIcon = mBubbleOverflow.getIconView(); + if (overflowIcon != null) { + PhysicsAnimationLayout parent = (PhysicsAnimationLayout) overflowIcon.getParent(); + if (parent != null) { + parent.removeViewNoAnimation(overflowIcon); + } + } + } + + /** * Holds some commonly queried information about the stack. */ public static class StackViewState { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 7a5815994dd0..da4a9898a44c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -15,6 +15,7 @@ */ package com.android.wm.shell.bubbles; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; @@ -110,6 +111,9 @@ public class BubbleTaskViewHelper { try { options.setTaskAlwaysOnTop(true); options.setLaunchedFromBubble(true); + options.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); Intent fillInIntent = new Intent(); // Apply flags to make behaviour match documentLaunchMode=always. @@ -117,11 +121,19 @@ public class BubbleTaskViewHelper { fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); if (mBubble.isAppBubble()) { - PendingIntent pi = PendingIntent.getActivity(mContext, 0, - mBubble.getAppBubbleIntent(), - PendingIntent.FLAG_MUTABLE, - null); - mTaskView.startActivity(pi, fillInIntent, options, launchBounds); + Context context = + mContext.createContextAsUser( + mBubble.getUser(), Context.CONTEXT_RESTRICTED); + PendingIntent pi = PendingIntent.getActivity( + context, + /* requestCode= */ 0, + mBubble.getAppBubbleIntent() + .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT) + .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK), + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, + /* options= */ null); + mTaskView.startActivity(pi, /* fillInIntent= */ null, options, + launchBounds); } else if (mBubble.hasMetadataShortcutId()) { options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index 8ab9841ff0c2..66e69300f45f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -104,7 +104,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask @Override protected BubbleViewInfo doInBackground(Void... voids) { - if (mController.get().isShowingAsBubbleBar()) { + if (!verifyState()) { + // If we're in an inconsistent state, then switched modes and should just bail now. + return null; + } + if (mLayerView.get() != null) { return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(), mLayerView.get(), mIconFactory, mBubble, mSkipInflation); } else { @@ -118,7 +122,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (isCancelled() || viewInfo == null) { return; } + mMainExecutor.execute(() -> { + if (!verifyState()) { + return; + } mBubble.setViewInfo(viewInfo); if (mCallback != null) { mCallback.onBubbleViewsReady(mBubble); @@ -126,6 +134,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask }); } + private boolean verifyState() { + if (mController.get().isShowingAsBubbleBar()) { + return mLayerView.get() != null; + } else { + return mStackView.get() != null; + } + } + /** * Info necessary to render a bubble. */ @@ -160,39 +176,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask LayoutInflater inflater = LayoutInflater.from(c); info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate( R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */); - info.bubbleBarExpandedView.initialize(controller); - } - - if (b.getShortcutInfo() != null) { - info.shortcutInfo = b.getShortcutInfo(); + info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */); } - // App name & app icon - PackageManager pm = BubbleController.getPackageManagerForUser(c, - b.getUser().getIdentifier()); - ApplicationInfo appInfo; - Drawable badgedIcon; - Drawable appIcon; - try { - appInfo = pm.getApplicationInfo( - b.getPackageName(), - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE); - if (appInfo != null) { - info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); - } - appIcon = pm.getApplicationIcon(b.getPackageName()); - badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); - } catch (PackageManager.NameNotFoundException exception) { - // If we can't find package... don't think we should show the bubble. - Log.w(TAG, "Unable to find package: " + b.getPackageName()); + if (!populateCommonInfo(info, c, b, iconFactory)) { + // if we failed to update common fields return null return null; } - info.rawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon; - return info; } @@ -215,66 +206,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask info.expandedView.initialize(controller, stackView, false /* isOverflow */); } - if (b.getShortcutInfo() != null) { - info.shortcutInfo = b.getShortcutInfo(); - } - - // App name & app icon - PackageManager pm = BubbleController.getPackageManagerForUser(c, - b.getUser().getIdentifier()); - ApplicationInfo appInfo; - Drawable badgedIcon; - Drawable appIcon; - try { - appInfo = pm.getApplicationInfo( - b.getPackageName(), - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE); - if (appInfo != null) { - info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); - } - appIcon = pm.getApplicationIcon(b.getPackageName()); - badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); - } catch (PackageManager.NameNotFoundException exception) { - // If we can't find package... don't think we should show the bubble. - Log.w(TAG, "Unable to find package: " + b.getPackageName()); + if (!populateCommonInfo(info, c, b, iconFactory)) { + // if we failed to update common fields return null return null; } - // Badged bubble image - Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, - b.getIcon()); - if (bubbleDrawable == null) { - // Default to app icon - bubbleDrawable = appIcon; - } - - BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon, - b.isImportantConversation()); - info.badgeBitmap = badgeBitmapInfo.icon; - // Raw badge bitmap never includes the important conversation ring - info.rawBadgeBitmap = b.isImportantConversation() - ? iconFactory.getBadgeBitmap(badgedIcon, false).icon - : badgeBitmapInfo.icon; - - float[] bubbleBitmapScale = new float[1]; - info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale); - - // Dot color & placement - Path iconPath = PathParser.createPathFromPathData( - c.getResources().getString(com.android.internal.R.string.config_icon_mask)); - Matrix matrix = new Matrix(); - float scale = bubbleBitmapScale[0]; - float radius = DEFAULT_PATH_SIZE / 2f; - matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, - radius /* pivot y */); - iconPath.transform(matrix); - info.dotPath = iconPath; - info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, - Color.WHITE, WHITE_SCRIM_ALPHA); - // Flyout info.flyoutMessage = b.getFlyoutMessage(); if (info.flyoutMessage != null) { @@ -285,6 +221,75 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask } } + /** + * Modifies the given {@code info} object and populates common fields in it. + * + * <p>This method returns {@code true} if the update was successful and {@code false} otherwise. + * Callers should assume that the info object is unusable if the update was unsuccessful. + */ + private static boolean populateCommonInfo( + BubbleViewInfo info, Context c, Bubble b, BubbleIconFactory iconFactory) { + if (b.getShortcutInfo() != null) { + info.shortcutInfo = b.getShortcutInfo(); + } + + // App name & app icon + PackageManager pm = BubbleController.getPackageManagerForUser(c, + b.getUser().getIdentifier()); + ApplicationInfo appInfo; + Drawable badgedIcon; + Drawable appIcon; + try { + appInfo = pm.getApplicationInfo( + b.getPackageName(), + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE); + if (appInfo != null) { + info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); + } + appIcon = pm.getApplicationIcon(b.getPackageName()); + badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); + } catch (PackageManager.NameNotFoundException exception) { + // If we can't find package... don't think we should show the bubble. + Log.w(TAG, "Unable to find package: " + b.getPackageName()); + return false; + } + + // Badged bubble image + Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, b.getIcon()); + if (bubbleDrawable == null) { + // Default to app icon + bubbleDrawable = appIcon; + } + + BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon, + b.isImportantConversation()); + info.badgeBitmap = badgeBitmapInfo.icon; + // Raw badge bitmap never includes the important conversation ring + info.rawBadgeBitmap = b.isImportantConversation() // is this needed for bar? + ? iconFactory.getBadgeBitmap(badgedIcon, false).icon + : badgeBitmapInfo.icon; + + float[] bubbleBitmapScale = new float[1]; + info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale); + + // Dot color & placement + Path iconPath = PathParser.createPathFromPathData( + c.getResources().getString(com.android.internal.R.string.config_icon_mask)); + Matrix matrix = new Matrix(); + float scale = bubbleBitmapScale[0]; + float radius = DEFAULT_PATH_SIZE / 2f; + matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, + radius /* pivot y */); + iconPath.transform(matrix); + info.dotPath = iconPath; + info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, + Color.WHITE, WHITE_SCRIM_ALPHA); + return true; + } + @Nullable static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { Objects.requireNonNull(context); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 4d329dd5d446..759246eb285d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -60,9 +60,11 @@ public interface Bubbles { DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE, DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT, DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED, - DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED}) + DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED, + DISMISS_SWITCH_TO_STACK}) @Target({FIELD, LOCAL_VARIABLE, PARAMETER}) - @interface DismissReason {} + @interface DismissReason { + } int DISMISS_USER_GESTURE = 1; int DISMISS_AGED = 2; @@ -80,6 +82,7 @@ public interface Bubbles { int DISMISS_NO_BUBBLE_UP = 14; int DISMISS_RELOAD_FROM_DISK = 15; int DISMISS_USER_REMOVED = 16; + int DISMISS_SWITCH_TO_STACK = 17; /** Returns a binder that can be passed to an external process to manipulate Bubbles. */ default IBubbles createExternalInterface() { @@ -120,8 +123,8 @@ public interface Bubbles { /** * This method has different behavior depending on: - * - if an app bubble exists - * - if an app bubble is expanded + * - if an app bubble exists + * - if an app bubble is expanded * * If no app bubble exists, this will add and expand a bubble with the provided intent. The * intent must be explicit (i.e. include a package name or fully qualified component class name) @@ -135,13 +138,13 @@ public interface Bubbles { * the bubble or bubble stack. * * Some notes: - * - Only one app bubble is supported at a time, regardless of users. Multi-users support is - * tracked in b/273533235. - * - Calling this method with a different intent than the existing app bubble will do nothing + * - Only one app bubble is supported at a time, regardless of users. Multi-users support is + * tracked in b/273533235. + * - Calling this method with a different intent than the existing app bubble will do nothing * * @param intent the intent to display in the bubble expanded view. - * @param user the {@link UserHandle} of the user to start this activity for. - * @param icon the {@link Icon} to use for the bubble view. + * @param user the {@link UserHandle} of the user to start this activity for. + * @param icon the {@link Icon} to use for the bubble view. */ void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon); @@ -172,13 +175,12 @@ public interface Bubbles { * {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add * {@link BubbleData#addSummaryToSuppress}. * - * @param entry the notification of the BubbleEntry should be removed. - * @param children the list of child notification of the BubbleEntry from 1st param entry, - * this will be null if entry does have no children. + * @param entry the notification of the BubbleEntry should be removed. + * @param children the list of child notification of the BubbleEntry from 1st param entry, + * this will be null if entry does have no children. * @param removeCallback the remove callback for SystemUI side to remove notification, the int * number should be list position of children list and use -1 for * removing the parent notification. - * * @return true if we want to intercept the dismissal of the entry, else false. */ boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, @@ -200,9 +202,9 @@ public interface Bubbles { /** * Called when new notification entry updated. * - * @param entry the {@link BubbleEntry} by the notification. + * @param entry the {@link BubbleEntry} by the notification. * @param shouldBubbleUp {@code true} if this notification should bubble up. - * @param fromSystem {@code true} if this update is from NotificationManagerService. + * @param fromSystem {@code true} if this update is from NotificationManagerService. */ void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem); @@ -218,7 +220,7 @@ public interface Bubbles { * filtering and sorting. This is used to dismiss or create bubbles based on changes in * permissions on the notification channel or the global setting. * - * @param rankingMap the updated ranking map from NotificationListenerService + * @param rankingMap the updated ranking map from NotificationListenerService * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should * bubble up */ @@ -230,9 +232,9 @@ public interface Bubbles { * Called when a notification channel is modified, in response to * {@link NotificationListenerService#onNotificationChannelModified}. * - * @param pkg the package the notification channel belongs to. - * @param user the user the notification channel belongs to. - * @param channel the channel being modified. + * @param pkg the package the notification channel belongs to. + * @param user the user the notification channel belongs to. + * @param channel the channel being modified. * @param modificationType the type of modification that occurred to the channel. */ void onNotificationChannelModified( @@ -300,7 +302,7 @@ public interface Bubbles { * Called when the expansion state of the bubble stack changes. * * @param isExpanding whether it's expanding or collapsing - * @param key the notification key associated with bubble being expanded + * @param key the notification key associated with bubble being expanded */ void onBubbleExpandChanged(boolean isExpanding, String key); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java index 3a3a378e00d3..137568458e3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java @@ -18,10 +18,10 @@ package com.android.wm.shell.bubbles; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.content.Context; import android.hardware.input.InputManager; -import android.util.Log; import android.view.Choreographer; import android.view.InputChannel; import android.view.InputEventReceiver; @@ -29,6 +29,7 @@ import android.view.InputMonitor; import androidx.annotation.Nullable; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener; /** @@ -58,9 +59,7 @@ class BubblesNavBarGestureTracker { * @param listener listener that is notified of touch events */ void start(MotionEventListener listener) { - if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) { - Log.d(TAG, "start monitoring bubbles swipe up gesture"); - } + ProtoLog.d(WM_SHELL_BUBBLES, "start monitoring bubbles swipe up gesture"); stopInternal(); @@ -76,9 +75,7 @@ class BubblesNavBarGestureTracker { } void stop() { - if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) { - Log.d(TAG, "stop monitoring bubbles swipe up gesture"); - } + ProtoLog.d(WM_SHELL_BUBBLES, "stop monitoring bubbles swipe up gesture"); stopInternal(); } @@ -94,9 +91,7 @@ class BubblesNavBarGestureTracker { } private void onInterceptTouch() { - if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) { - Log.d(TAG, "intercept touch event"); - } + ProtoLog.d(WM_SHELL_BUBBLES, "intercept touch event"); if (mInputMonitor != null) { mInputMonitor.pilferPointers(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java index 844526ca0f35..b7107f09b17f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java @@ -16,19 +16,20 @@ package com.android.wm.shell.bubbles; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.content.Context; import android.graphics.PointF; -import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; import androidx.annotation.Nullable; +import com.android.internal.protolog.common.ProtoLog; + /** * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area */ @@ -112,10 +113,8 @@ class BubblesNavBarMotionEventHandler { private boolean isInGestureRegion(MotionEvent ev) { // Only handles touch events beginning in navigation bar system gesture zone if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) { - if (DEBUG_BUBBLE_GESTURE) { - Log.d(TAG, "handling touch y=" + ev.getY() - + " navBarGestureZone=" + mPositioner.getNavBarGestureZone()); - } + ProtoLog.d(WM_SHELL_BUBBLES, "handling touch x=%d y=%d navBarGestureZone=%s", + (int) ev.getX(), (int) ev.getY(), mPositioner.getNavBarGestureZone()); return true; } return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java new file mode 100644 index 000000000000..9e8a385262e4 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + +import android.app.ActivityManager; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +import androidx.annotation.NonNull; + +import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; + +/** + * Observer used to identify tasks that are opening or moving to front. If a bubble activity is + * currently opened when this happens, we'll collapse the bubbles. + */ +public class BubblesTransitionObserver implements Transitions.TransitionObserver { + + private BubbleController mBubbleController; + private BubbleData mBubbleData; + + public BubblesTransitionObserver(BubbleController controller, + BubbleData bubbleData) { + mBubbleController = controller; + mBubbleData = bubbleData; + } + + @Override + public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + for (TransitionInfo.Change change : info.getChanges()) { + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + // We only care about opens / move to fronts when bubbles are expanded & not animating. + if (taskInfo == null + || taskInfo.taskId == INVALID_TASK_ID + || !TransitionUtil.isOpeningType(change.getMode()) + || mBubbleController.isStackAnimating() + || !mBubbleData.isExpanded() + || mBubbleData.getSelectedBubble() == null) { + continue; + } + int expandedId = mBubbleData.getSelectedBubble().getTaskId(); + // If the task id that's opening is the same as the expanded bubble, skip collapsing + // because it is our bubble that is opening. + if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) { + mBubbleData.setExpanded(false); + } + } + } + + @Override + public void onTransitionStarting(@NonNull IBinder transition) { + + } + + @Override + public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) { + + } + + @Override + public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) { + + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt new file mode 100644 index 000000000000..48692d41016e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt @@ -0,0 +1,34 @@ +/* + * 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. + */ +@file:JvmName("DismissViewUtils") + +package com.android.wm.shell.bubbles + +import com.android.wm.shell.R +import com.android.wm.shell.common.bubbles.DismissView + +fun DismissView.setup() { + setup(DismissView.Config( + dismissViewResId = R.id.dismiss_view, + targetSizeResId = R.dimen.dismiss_circle_size, + iconSizeResId = R.dimen.dismiss_target_x_size, + bottomMarginResId = R.dimen.floating_dismiss_bottom_margin, + floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height, + floatingGradientColorResId = android.R.color.system_neutral1_900, + backgroundResId = R.drawable.dismiss_circle_background, + iconResId = R.drawable.pip_ic_close_white + )) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 862e818a998b..5776ad109d19 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -29,12 +29,16 @@ interface IBubbles { oneway void unregisterBubbleListener(in IBubblesListener listener) = 2; - oneway void showBubble(in String key, in boolean onLauncherHome) = 3; + oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3; - oneway void removeBubble(in String key, in int reason) = 4; + oneway void removeBubble(in String key) = 4; - oneway void collapseBubbles() = 5; + oneway void removeAllBubbles() = 5; - oneway void onTaskbarStateChanged(in int newState) = 6; + oneway void collapseBubbles() = 6; + + oneway void onBubbleDrag(in String key, in boolean isBeingDragged) = 7; + + oneway void showUserEducation(in int positionX, in int positionY) = 8; }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt index e95e8e5cdaea..1b41f793311d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt @@ -41,9 +41,9 @@ class ManageEducationView constructor(context: Context, positioner: BubblePositi private val ANIMATE_DURATION: Long = 200 private val positioner: BubblePositioner = positioner - private val manageView by lazy { findViewById<ViewGroup>(R.id.manage_education_view) } - private val manageButton by lazy { findViewById<Button>(R.id.manage_button) } - private val gotItButton by lazy { findViewById<Button>(R.id.got_it) } + private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) } + private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) } + private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) } private var isHiding = false private var realManageButtonRect = Rect() @@ -122,7 +122,7 @@ class ManageEducationView constructor(context: Context, positioner: BubblePositi manageButton .setOnClickListener { hide() - expandedView.findViewById<View>(R.id.manage_button).performClick() + expandedView.requireViewById<View>(R.id.manage_button).performClick() } gotItButton.setOnClickListener { hide() } setOnClickListener { hide() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt index d0598cd28582..5e3a077a3716 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt @@ -48,9 +48,9 @@ class StackEducationView constructor( private val positioner: BubblePositioner = positioner private val controller: BubbleController = controller - private val view by lazy { findViewById<View>(R.id.stack_education_layout) } - private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) } - private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) } + private val view by lazy { requireViewById<View>(R.id.stack_education_layout) } + private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) } + private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) } var isHiding = false private set diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java index 33629f9f4622..4d7042bbb3d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java @@ -19,7 +19,6 @@ package com.android.wm.shell.bubbles.animation; import static android.view.View.LAYOUT_DIRECTION_RTL; import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; -import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE; import android.content.res.Resources; import android.graphics.Path; @@ -132,6 +131,16 @@ public class ExpandedAnimationController private BubbleStackView mBubbleStackView; + /** + * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause + * the rest of the bubbles to animate to fill the gap. + */ + private boolean mBubbleDraggedOutEnough = false; + + /** End action to run when the lead bubble's expansion animation completes. */ + @Nullable + private Runnable mLeadBubbleEndAction; + public ExpandedAnimationController(BubblePositioner positioner, Runnable onBubbleAnimatedOutAction, BubbleStackView stackView) { mPositioner = positioner; @@ -142,14 +151,12 @@ public class ExpandedAnimationController } /** - * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause - * the rest of the bubbles to animate to fill the gap. + * Overrides the collapse location without actually collapsing the stack. + * @param point the new collapse location. */ - private boolean mBubbleDraggedOutEnough = false; - - /** End action to run when the lead bubble's expansion animation completes. */ - @Nullable - private Runnable mLeadBubbleEndAction; + public void setCollapsePoint(PointF point) { + mCollapsePoint = point; + } /** * Animates expanding the bubbles into a row along the top of the screen, optionally running an @@ -355,7 +362,6 @@ public class ExpandedAnimationController mMagnetizedBubbleDraggingOut.setMagnetListener(listener); mMagnetizedBubbleDraggingOut.setHapticsEnabled(true); mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); - mMagnetizedBubbleDraggingOut.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE); } private void springBubbleTo(View bubble, float x, float y) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java index beb1c5fa6c10..f3cc514d2972 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java @@ -327,6 +327,12 @@ public class PhysicsAnimationLayout extends FrameLayout { addViewInternal(child, index, params, false /* isReorder */); } + /** Removes the child view immediately. */ + public void removeViewNoAnimation(View view) { + super.removeView(view); + view.setTag(R.id.physics_animator_tag, null); + } + @Override public void removeView(View view) { if (mController != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index 5533842f2d89..aad268394305 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -17,7 +17,6 @@ package com.android.wm.shell.bubbles.animation; import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; -import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE; import android.content.ContentResolver; import android.content.res.Resources; @@ -298,9 +297,6 @@ public class StackAnimationController extends /** Whether the stack is on the left side of the screen. */ public boolean isStackOnLeftSide() { - if (mLayout == null || !isStackPositionSet()) { - return true; // Default to left, which is where it starts by default. - } return mPositioner.isStackOnLeft(mStackPosition); } @@ -1026,7 +1022,6 @@ public class StackAnimationController extends }; mMagnetizedStack.setHapticsEnabled(true); mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); - mMagnetizedStack.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE); } final ContentResolver contentResolver = mLayout.getContext().getContentResolver(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 23f65f943aa4..689323b725ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -21,12 +21,15 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; -import android.graphics.PointF; +import android.graphics.Point; import android.util.Log; import android.widget.FrameLayout; +import androidx.annotation.Nullable; + import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix; @@ -110,7 +113,8 @@ public class BubbleBarAnimationHelper { /** * Animates the provided bubble's expanded view to the expanded state. */ - public void animateExpansion(BubbleViewProvider expandedBubble) { + public void animateExpansion(BubbleViewProvider expandedBubble, + @Nullable Runnable afterAnimation) { mExpandedBubble = expandedBubble; if (mExpandedBubble == null) { return; @@ -132,7 +136,7 @@ public class BubbleBarAnimationHelper { bev.setVisibility(VISIBLE); // Set the pivot point for the scale, so the view animates out from the bubble bar. - PointF bubbleBarPosition = mPositioner.getBubbleBarPosition(); + Point bubbleBarPosition = mPositioner.getBubbleBarPosition(); mExpandedViewContainerMatrix.setScale( 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, @@ -159,6 +163,9 @@ public class BubbleBarAnimationHelper { bev.setAnimationMatrix(null); updateExpandedView(); bev.setSurfaceZOrderedOnTop(false); + if (afterAnimation != null) { + afterAnimation.run(); + } }) .start(); } @@ -208,6 +215,14 @@ public class BubbleBarAnimationHelper { mExpandedViewAlphaAnimator.reverse(); } + /** + * Cancel current animations + */ + public void cancelAnimations() { + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); + mExpandedViewAlphaAnimator.cancel(); + } + private void updateExpandedView() { if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) { Log.w(TAG, "Trying to update the expanded view without a bubble"); @@ -215,9 +230,10 @@ public class BubbleBarAnimationHelper { } BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView(); + boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); final int padding = mPositioner.getBubbleBarExpandedViewPadding(); - final int width = mPositioner.getExpandedViewWidthForBubbleBar(); - final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams(); lp.width = width; lp.height = height; @@ -227,7 +243,8 @@ public class BubbleBarAnimationHelper { } else { bbev.setX(mPositioner.getAvailableRect().width() - width - padding); } - bbev.setY(mPositioner.getInsets().top + padding); + bbev.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height); bbev.updateLocation(); + bbev.maybeShowOverflow(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index b8f049becb6f..79f188ab2611 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -16,12 +16,16 @@ package com.android.wm.shell.bubbles.bar; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.Insets; import android.graphics.Outline; +import android.graphics.Rect; import android.util.AttributeSet; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewOutlineProvider; import android.widget.FrameLayout; @@ -30,26 +34,48 @@ import com.android.internal.policy.ScreenDecorationsUtils; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.bubbles.BubbleOverflowContainerView; import com.android.wm.shell.bubbles.BubbleTaskViewHelper; +import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.taskview.TaskView; +import java.util.function.Supplier; + /** * Expanded view of a bubble when it's part of the bubble bar. * * {@link BubbleController#isShowingAsBubbleBar()} */ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener { + /** + * The expanded view listener notifying the {@link BubbleBarLayerView} about the internal + * actions and events + */ + public interface Listener { + /** Called when the task view task is first created. */ + void onTaskCreated(); + /** Called when expanded view needs to un-bubble the given conversation */ + void onUnBubbleConversation(String bubbleKey); + /** Called when expanded view task view back button pressed */ + void onBackPressed(); + } private static final String TAG = BubbleBarExpandedView.class.getSimpleName(); private static final int INVALID_TASK_ID = -1; private BubbleController mController; + private boolean mIsOverflow; private BubbleTaskViewHelper mBubbleTaskViewHelper; + private BubbleBarMenuViewController mMenuViewController; + private @Nullable Supplier<Rect> mLayerBoundsSupplier; + private @Nullable Listener mListener; - private HandleView mMenuView; - private TaskView mTaskView; + private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext()); + private @Nullable TaskView mTaskView; + private @Nullable BubbleOverflowContainerView mOverflowView; + + private int mCaptionHeight; - private int mMenuHeight; private int mBackgroundColor; private float mCornerRadius = 0f; @@ -83,11 +109,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView super.onFinishInflate(); Context context = getContext(); setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation)); - mMenuHeight = context.getResources().getDimensionPixelSize( - R.dimen.bubblebar_expanded_view_menu_size); - mMenuView = new HandleView(context); - addView(mMenuView); - + mCaptionHeight = context.getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_height); + addView(mHandleView); applyThemeAttrs(); setClipToOutline(true); setOutlineProvider(new ViewOutlineProvider() { @@ -98,23 +122,76 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView }); } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + // Hide manage menu when view disappears + mMenuViewController.hideMenu(false /* animated */); + } + /** Set the BubbleController on the view, must be called before doing anything else. */ - public void initialize(BubbleController controller) { + public void initialize(BubbleController controller, boolean isOverflow) { mController = controller; - mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController, - /* listener= */ this, - /* viewParent= */ this); - mTaskView = mBubbleTaskViewHelper.getTaskView(); - addView(mTaskView); - mTaskView.setEnableSurfaceClipping(true); - mTaskView.setCornerRadius(mCornerRadius); + mIsOverflow = isOverflow; + + if (mIsOverflow) { + mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate( + R.layout.bubble_overflow_container, null /* root */); + mOverflowView.setBubbleController(mController); + addView(mOverflowView); + } else { + + mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController, + /* listener= */ this, + /* viewParent= */ this); + mTaskView = mBubbleTaskViewHelper.getTaskView(); + addView(mTaskView); + mTaskView.setEnableSurfaceClipping(true); + mTaskView.setCornerRadius(mCornerRadius); + + // Handle view needs to draw on top of task view. + bringChildToFront(mHandleView); + } + mMenuViewController = new BubbleBarMenuViewController(mContext, this); + mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() { + @Override + public void onMenuVisibilityChanged(boolean visible) { + setObscured(visible); + } + + @Override + public void onUnBubbleConversation(Bubble bubble) { + if (mListener != null) { + mListener.onUnBubbleConversation(bubble.getKey()); + } + } + + @Override + public void onOpenAppSettings(Bubble bubble) { + mController.collapseStack(); + mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser()); + } + + @Override + public void onDismissBubble(Bubble bubble) { + mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED); + } + }); + mHandleView.setOnClickListener(view -> { + mMenuViewController.showMenu(true /* animated */); + }); + } + + public BubbleBarHandleView getHandleView() { + return mHandleView; } // TODO (b/275087636): call this when theme/config changes - void applyThemeAttrs() { + /** Updates the view based on the current theme. */ + public void applyThemeAttrs() { boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( mContext.getResources()); - final TypedArray ta = mContext.obtainStyledAttributes(new int[] { + final TypedArray ta = mContext.obtainStyledAttributes(new int[]{ android.R.attr.dialogCornerRadius, android.R.attr.colorBackgroundFloating}); mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0; @@ -123,14 +200,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView ta.recycle(); - mMenuView.setCornerRadius(mCornerRadius); - mMenuHeight = getResources().getDimensionPixelSize( - R.dimen.bubblebar_expanded_view_menu_size); + mCaptionHeight = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_height); if (mTaskView != null) { mTaskView.setCornerRadius(mCornerRadius); - mTaskView.setElevation(150); - updateMenuColor(); + updateHandleColor(true /* animated */); } } @@ -138,35 +213,36 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); - - // Add corner radius here so that the menu extends behind the rounded corners of TaskView. - int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height); - measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, + int menuViewHeight = Math.min(mCaptionHeight, height); + measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, MeasureSpec.getMode(heightMeasureSpec))); if (mTaskView != null) { - int taskViewHeight = height - menuViewHeight; - measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight, + measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec))); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - // Drag handle above - final int dragHandleBottom = t + mMenuView.getMeasuredHeight(); - mMenuView.layout(l, t, r, dragHandleBottom); + super.onLayout(changed, l, t, r, b); + final int captionBottom = t + mCaptionHeight; if (mTaskView != null) { - // Subtract radius so that the menu extends behind the rounded corners of TaskView. - mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r, - dragHandleBottom + mTaskView.getMeasuredHeight()); + mTaskView.layout(l, t, r, + t + mTaskView.getMeasuredHeight()); + mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0)); } + // Handle draws on top of task view in the caption area. + mHandleView.layout(l, t, r, captionBottom); } @Override public void onTaskCreated() { setContentVisibility(true); - updateMenuColor(); + updateHandleColor(false /* animated */); + if (mListener != null) { + mListener.onTaskCreated(); + } } @Override @@ -176,7 +252,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Override public void onBackPressed() { - mController.collapseStack(); + if (mListener == null) return; + mListener.onBackPressed(); } /** Cleans up task view, should be called when the bubble is no longer active. */ @@ -187,11 +264,25 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } mBubbleTaskViewHelper.cleanUpTaskView(); } + mMenuViewController.hideMenu(false /* animated */); + } + + /** + * Hides the current modal menu view or collapses the bubble stack. + * Called from {@link BubbleBarLayerView} + */ + public void hideMenuOrCollapse() { + if (mMenuViewController.isMenuVisible()) { + mMenuViewController.hideMenu(/* animated = */ true); + } else { + mController.collapseStack(); + } } - /** Updates the bubble shown in this task view. */ + /** Updates the bubble shown in the expanded view. */ public void update(Bubble bubble) { mBubbleTaskViewHelper.update(bubble); + mMenuViewController.updateMenu(bubble); } /** The task id of the activity shown in the task view, if it exists. */ @@ -199,12 +290,39 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID; } + /** Sets layer bounds supplier used for obscured touchable region of task view */ + void setLayerBoundsSupplier(@Nullable Supplier<Rect> supplier) { + mLayerBoundsSupplier = supplier; + } + + /** Sets expanded view listener */ + void setListener(@Nullable Listener listener) { + mListener = listener; + } + + /** Sets whether the view is obscured by some modal view */ + void setObscured(boolean obscured) { + if (mTaskView == null || mLayerBoundsSupplier == null) return; + // Updates the obscured touchable region for the task surface. + mTaskView.setObscuredTouchRect(obscured ? mLayerBoundsSupplier.get() : null); + } + /** * Call when the location or size of the view has changed to update TaskView. */ public void updateLocation() { - if (mTaskView == null) return; - mTaskView.onLocationChanged(); + if (mTaskView != null) { + mTaskView.onLocationChanged(); + } + } + + /** Shows the expanded view for the overflow if it exists. */ + void maybeShowOverflow() { + if (mOverflowView != null) { + // post this to the looper so that the view has a chance to be laid out before it can + // calculate row and column sizes correctly. + post(() -> mOverflowView.show()); + } } /** Sets the alpha of the task view. */ @@ -218,17 +336,21 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } } - /** Updates the menu bar to be the status bar color specified by the app. */ - private void updateMenuColor() { - if (mTaskView == null) return; - ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo(); - final int taskBgColor = info.taskDescription.getStatusBarColor(); - final int color = Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); - if (color != -1) { - mMenuView.setBackgroundColor(color); - } else { - mMenuView.setBackgroundColor(mBackgroundColor); + /** + * Updates the handle color based on the task view status bar or background color; if those + * are transparent it defaults to the background color pulled from system theme attributes. + */ + private void updateHandleColor(boolean animated) { + if (mTaskView == null || mTaskView.getTaskInfo() == null) return; + int color = mBackgroundColor; + ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription; + if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) { + color = taskDescription.getStatusBarColor(); + } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) { + color = taskDescription.getBackgroundColor(); } + final boolean isRegionDark = Color.luminance(color) <= 0.5; + mHandleView.updateHandleColor(isRegionDark, animated); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java new file mode 100644 index 000000000000..2b7a0706b4de --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles.bar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Outline; +import android.graphics.Path; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOutlineProvider; + +import androidx.annotation.ColorInt; +import androidx.core.content.ContextCompat; + +import com.android.wm.shell.R; + +/** + * Handle view to show at the top of a bubble bar expanded view. + */ +public class BubbleBarHandleView extends View { + private static final long COLOR_CHANGE_DURATION = 120; + + // The handle view is currently rendered as 3 evenly spaced dots. + private int mDotSize; + private int mDotSpacing; + // Path used to draw the dots + private final Path mPath = new Path(); + + private @ColorInt int mHandleLightColor; + private @ColorInt int mHandleDarkColor; + private @Nullable ObjectAnimator mColorChangeAnim; + + public BubbleBarHandleView(Context context) { + this(context, null /* attrs */); + } + + public BubbleBarHandleView(Context context, AttributeSet attrs) { + this(context, attrs, 0 /* defStyleAttr */); + } + + public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0 /* defStyleRes */); + } + + public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mDotSize = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_dot_size); + mDotSpacing = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_dot_spacing); + mHandleLightColor = ContextCompat.getColor(getContext(), + R.color.bubble_bar_expanded_view_handle_light); + mHandleDarkColor = ContextCompat.getColor(getContext(), + R.color.bubble_bar_expanded_view_handle_dark); + + setClipToOutline(true); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + final int handleCenterX = view.getWidth() / 2; + final int handleCenterY = view.getHeight() / 2; + final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2; + final int handleLeft = handleCenterX - handleTotalWidth / 2; + final int handleTop = handleCenterY - mDotSize / 2; + final int handleBottom = handleTop + mDotSize; + RectF dot1 = new RectF( + handleLeft, handleTop, + handleLeft + mDotSize, handleBottom); + RectF dot2 = new RectF( + dot1.right + mDotSpacing, handleTop, + dot1.right + mDotSpacing + mDotSize, handleBottom + ); + RectF dot3 = new RectF( + dot2.right + mDotSpacing, handleTop, + dot2.right + mDotSpacing + mDotSize, handleBottom + ); + mPath.reset(); + mPath.addOval(dot1, Path.Direction.CW); + mPath.addOval(dot2, Path.Direction.CW); + mPath.addOval(dot3, Path.Direction.CW); + outline.setPath(mPath); + } + }); + } + + /** + * Updates the handle color. + * + * @param isRegionDark Whether the background behind the handle is dark, and thus the handle + * should be light (and vice versa). + * @param animated Whether to animate the change, or apply it immediately. + */ + public void updateHandleColor(boolean isRegionDark, boolean animated) { + int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor; + if (mColorChangeAnim != null) { + mColorChangeAnim.cancel(); + } + if (animated) { + mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor); + mColorChangeAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mColorChangeAnim = null; + } + }); + mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION); + mColorChangeAnim.start(); + } else { + setBackgroundColor(newColor); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index b1a725b6e5c4..e788341df5f8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -21,17 +21,24 @@ import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT; import android.annotation.Nullable; import android.content.Context; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; +import android.view.TouchDelegate; import android.view.View; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; +import java.util.function.Consumer; + +import kotlin.Unit; + /** * Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window * manager to display bubbles. However, it is only used when bubbles are being displayed in @@ -48,11 +55,13 @@ public class BubbleBarLayerView extends FrameLayout private final BubbleController mBubbleController; private final BubblePositioner mPositioner; private final BubbleBarAnimationHelper mAnimationHelper; + private final BubbleEducationViewController mEducationViewController; private final View mScrimView; @Nullable private BubbleViewProvider mExpandedBubble; private BubbleBarExpandedView mExpandedView; + private @Nullable Consumer<String> mUnBubbleConversationCallback; // TODO(b/273310265) - currently the view is always on the right, need to update for RTL. /** Whether the expanded view is displaying on the left of the screen or not. */ @@ -64,6 +73,10 @@ public class BubbleBarLayerView extends FrameLayout private final Region mTouchableRegion = new Region(); private final Rect mTempRect = new Rect(); + // Used to ensure touch target size for the menu shown on a bubble expanded view + private TouchDelegate mHandleTouchDelegate; + private final Rect mHandleTouchBounds = new Rect(); + public BubbleBarLayerView(Context context, BubbleController controller) { super(context); mBubbleController = controller; @@ -71,6 +84,10 @@ public class BubbleBarLayerView extends FrameLayout mAnimationHelper = new BubbleBarAnimationHelper(context, this, mPositioner); + mEducationViewController = new BubbleEducationViewController(context, (boolean visible) -> { + if (mExpandedView == null) return; + mExpandedView.setObscured(visible); + }); mScrimView = new View(getContext()); mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); @@ -81,9 +98,7 @@ public class BubbleBarLayerView extends FrameLayout mScrimView.setBackgroundDrawable(new ColorDrawable( getResources().getColor(android.R.color.system_neutral1_1000))); - setOnClickListener(view -> { - mBubbleController.collapseStack(); - }); + setOnClickListener(view -> hideMenuOrCollapse()); } @Override @@ -99,6 +114,7 @@ public class BubbleBarLayerView extends FrameLayout getViewTreeObserver().removeOnComputeInternalInsetsListener(this); if (mExpandedView != null) { + mEducationViewController.hideEducation(/* animated = */ false); removeView(mExpandedView); mExpandedView = null; } @@ -141,17 +157,61 @@ public class BubbleBarLayerView extends FrameLayout mExpandedView = null; } if (mExpandedView == null) { + if (expandedView.getParent() != null) { + // Expanded view might be animating collapse and is still attached + // Cancel current animations and remove from parent + mAnimationHelper.cancelAnimations(); + removeView(expandedView); + } mExpandedBubble = b; mExpandedView = expandedView; - final int width = mPositioner.getExpandedViewWidthForBubbleBar(); - final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + boolean isOverflowExpanded = b.getKey().equals(BubbleOverflow.KEY); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); mExpandedView.setVisibility(GONE); + mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height); + mExpandedView.setLayerBoundsSupplier(() -> new Rect(0, 0, getWidth(), getHeight())); + mExpandedView.setListener(new BubbleBarExpandedView.Listener() { + @Override + public void onTaskCreated() { + if (mEducationViewController != null && mExpandedView != null) { + mEducationViewController.maybeShowManageEducation(b, mExpandedView); + } + } + + @Override + public void onUnBubbleConversation(String bubbleKey) { + if (mUnBubbleConversationCallback != null) { + mUnBubbleConversationCallback.accept(bubbleKey); + } + } + + @Override + public void onBackPressed() { + hideMenuOrCollapse(); + } + }); + addView(mExpandedView, new FrameLayout.LayoutParams(width, height)); } + if (mEducationViewController.isEducationVisible()) { + mEducationViewController.hideEducation(/* animated = */ true); + } + mIsExpanded = true; mBubbleController.getSysuiProxy().onStackExpandChanged(true); - mAnimationHelper.animateExpansion(mExpandedBubble); + mAnimationHelper.animateExpansion(mExpandedBubble, () -> { + if (mExpandedView == null) return; + // Touch delegate for the menu + BubbleBarHandleView view = mExpandedView.getHandleView(); + view.getBoundsOnScreen(mHandleTouchBounds); + mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop(); + mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds, + mExpandedView.getHandleView()); + setTouchDelegate(mHandleTouchDelegate); + }); + showScrim(true); } @@ -159,18 +219,53 @@ public class BubbleBarLayerView extends FrameLayout public void collapse() { mIsExpanded = false; final BubbleBarExpandedView viewToRemove = mExpandedView; + mEducationViewController.hideEducation(/* animated = */ true); mAnimationHelper.animateCollapse(() -> removeView(viewToRemove)); mBubbleController.getSysuiProxy().onStackExpandChanged(false); mExpandedView = null; + setTouchDelegate(null); showScrim(false); } + /** + * Show bubble bar user education relative to the reference position. + * @param position the reference position in Screen coordinates. + */ + public void showUserEducation(Point position) { + mEducationViewController.showStackEducation(position, /* root = */ this, () -> { + // When the user education is clicked hide it and expand the selected bubble + mEducationViewController.hideEducation(/* animated = */ true, () -> { + mBubbleController.expandStackWithSelectedBubble(); + return Unit.INSTANCE; + }); + return Unit.INSTANCE; + }); + } + + /** Sets the function to call to un-bubble the given conversation. */ + public void setUnBubbleConversationCallback( + @Nullable Consumer<String> unBubbleConversationCallback) { + mUnBubbleConversationCallback = unBubbleConversationCallback; + } + + /** Hides the current modal education/menu view, expanded view or collapses the bubble stack */ + private void hideMenuOrCollapse() { + if (mEducationViewController.isEducationVisible()) { + mEducationViewController.hideEducation(/* animated = */ true); + } else if (isExpanded() && mExpandedView != null) { + mExpandedView.hideMenuOrCollapse(); + } else { + mBubbleController.collapseStack(); + } + } + /** Updates the expanded view size and position. */ private void updateExpandedView() { if (mExpandedView == null) return; + boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); final int padding = mPositioner.getBubbleBarExpandedViewPadding(); - final int width = mPositioner.getExpandedViewWidthForBubbleBar(); - final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); FrameLayout.LayoutParams lp = (LayoutParams) mExpandedView.getLayoutParams(); lp.width = width; lp.height = height; @@ -180,7 +275,7 @@ public class BubbleBarLayerView extends FrameLayout } else { mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding); } - mExpandedView.setY(mPositioner.getInsets().top + padding); + mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height); mExpandedView.updateLocation(); } @@ -204,7 +299,7 @@ public class BubbleBarLayerView extends FrameLayout */ private void getTouchableRegion(Region outRegion) { mTempRect.setEmpty(); - if (mIsExpanded) { + if (mIsExpanded || mEducationViewController.isEducationVisible()) { getBoundsOnScreen(mTempRect); outRegion.op(mTempRect, Region.Op.UNION); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java new file mode 100644 index 000000000000..00b977721bea --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles.bar; + +import android.annotation.ColorInt; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.Icon; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.wm.shell.R; + +/** + * Bubble bar expanded view menu item view to display menu action details + */ +public class BubbleBarMenuItemView extends LinearLayout { + private ImageView mImageView; + private TextView mTextView; + + public BubbleBarMenuItemView(Context context) { + this(context, null /* attrs */); + } + + public BubbleBarMenuItemView(Context context, AttributeSet attrs) { + this(context, attrs, 0 /* defStyleAttr */); + } + + public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0 /* defStyleRes */); + } + + public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mImageView = findViewById(R.id.bubble_bar_menu_item_icon); + mTextView = findViewById(R.id.bubble_bar_menu_item_title); + } + + /** + * Update menu item with the details and tint color + */ + void update(Icon icon, String title, @ColorInt int tint) { + if (tint == Color.TRANSPARENT) { + final TypedArray typedArray = getContext().obtainStyledAttributes( + new int[]{android.R.attr.textColorPrimary}); + mTextView.setTextColor(typedArray.getColor(0, Color.BLACK)); + } else { + icon.setTint(tint); + mTextView.setTextColor(tint); + } + + mImageView.setImageIcon(icon); + mTextView.setText(title); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java new file mode 100644 index 000000000000..211fe0d48e43 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles.bar; + +import android.annotation.ColorInt; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.Icon; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.wm.shell.R; +import com.android.wm.shell.bubbles.Bubble; + +import java.util.ArrayList; + +/** + * Bubble bar expanded view menu + */ +public class BubbleBarMenuView extends LinearLayout { + private ViewGroup mBubbleSectionView; + private ViewGroup mActionsSectionView; + private ImageView mBubbleIconView; + private TextView mBubbleTitleView; + + public BubbleBarMenuView(Context context) { + this(context, null /* attrs */); + } + + public BubbleBarMenuView(Context context, AttributeSet attrs) { + this(context, attrs, 0 /* defStyleAttr */); + } + + public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0 /* defStyleRes */); + } + + public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mBubbleSectionView = findViewById(R.id.bubble_bar_manage_menu_bubble_section); + mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section); + mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon); + mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title); + } + + /** Update menu details with bubble info */ + void updateInfo(Bubble bubble) { + if (bubble.getIcon() != null) { + mBubbleIconView.setImageIcon(bubble.getIcon()); + } else { + mBubbleIconView.setImageBitmap(bubble.getBubbleIcon()); + } + mBubbleTitleView.setText(bubble.getTitle()); + } + + /** + * Update menu action items views + * @param actions used to populate menu item views + */ + void updateActions(ArrayList<MenuAction> actions) { + mActionsSectionView.removeAllViews(); + LayoutInflater inflater = LayoutInflater.from(mContext); + + for (MenuAction action : actions) { + BubbleBarMenuItemView itemView = (BubbleBarMenuItemView) inflater.inflate( + R.layout.bubble_bar_menu_item, mActionsSectionView, false); + itemView.update(action.mIcon, action.mTitle, action.mTint); + itemView.setOnClickListener(action.mOnClick); + mActionsSectionView.addView(itemView); + } + } + + /** Sets on close menu listener */ + void setOnCloseListener(Runnable onClose) { + mBubbleSectionView.setOnClickListener(view -> { + onClose.run(); + }); + } + + /** + * Overridden to proxy to section views alpha. + * @implNote + * If animate alpha on the parent (menu container) view, section view shadows get distorted. + * To prevent distortion and artifacts alpha changes applied directly on the section views. + */ + @Override + public void setAlpha(float alpha) { + mBubbleSectionView.setAlpha(alpha); + mActionsSectionView.setAlpha(alpha); + } + + /** + * Overridden to proxy section view alpha value. + * @implNote + * The assumption is that both section views have the same alpha value + */ + @Override + public float getAlpha() { + return mBubbleSectionView.getAlpha(); + } + + /** + * Menu action details used to create menu items + */ + static class MenuAction { + private Icon mIcon; + private @ColorInt int mTint; + private String mTitle; + private OnClickListener mOnClick; + + MenuAction(Icon icon, String title, OnClickListener onClick) { + this(icon, title, Color.TRANSPARENT, onClick); + } + + MenuAction(Icon icon, String title, @ColorInt int tint, OnClickListener onClick) { + this.mIcon = icon; + this.mTitle = title; + this.mTint = tint; + this.mOnClick = onClick; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java new file mode 100644 index 000000000000..81e7582e0dba --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles.bar; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Icon; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.core.content.ContextCompat; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.wm.shell.R; +import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.bubbles.Bubble; + +import java.util.ArrayList; + +/** + * Manages bubble bar expanded view menu presentation and animations + */ +class BubbleBarMenuViewController { + private static final float MENU_INITIAL_SCALE = 0.5f; + private final Context mContext; + private final ViewGroup mRootView; + private @Nullable Listener mListener; + private @Nullable Bubble mBubble; + private @Nullable BubbleBarMenuView mMenuView; + /** A transparent view used to intercept touches to collapse menu when presented */ + private @Nullable View mScrimView; + private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator; + private PhysicsAnimator.SpringConfig mMenuSpringConfig; + + BubbleBarMenuViewController(Context context, ViewGroup rootView) { + mContext = context; + mRootView = rootView; + mMenuSpringConfig = new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); + } + + /** Tells if the menu is visible or being animated */ + boolean isMenuVisible() { + return mMenuView != null && mMenuView.getVisibility() == View.VISIBLE; + } + + /** Sets menu actions listener */ + void setListener(@Nullable Listener listener) { + mListener = listener; + } + + /** Update menu with bubble */ + void updateMenu(@NonNull Bubble bubble) { + mBubble = bubble; + } + + /** + * Show bubble bar expanded view menu + * @param animated if should animate transition + */ + void showMenu(boolean animated) { + if (mMenuView == null || mScrimView == null) { + setupMenu(); + } + cancelAnimations(); + mMenuView.setVisibility(View.VISIBLE); + mScrimView.setVisibility(View.VISIBLE); + Runnable endActions = () -> { + mMenuView.getChildAt(0).requestAccessibilityFocus(); + if (mListener != null) { + mListener.onMenuVisibilityChanged(true /* isShown */); + } + }; + if (animated) { + animateTransition(true /* show */, endActions); + } else { + endActions.run(); + } + } + + /** + * Hide bubble bar expanded view menu + * @param animated if should animate transition + */ + void hideMenu(boolean animated) { + if (mMenuView == null || mScrimView == null) return; + cancelAnimations(); + Runnable endActions = () -> { + mMenuView.setVisibility(View.GONE); + mScrimView.setVisibility(View.GONE); + if (mListener != null) { + mListener.onMenuVisibilityChanged(false /* isShown */); + } + }; + if (animated) { + animateTransition(false /* show */, endActions); + } else { + endActions.run(); + } + } + + /** + * Animate show/hide menu transition + * @param show if should show or hide the menu + * @param endActions will be called when animation ends + */ + private void animateTransition(boolean show, Runnable endActions) { + if (mMenuView == null) return; + mMenuAnimator = PhysicsAnimator.getInstance(mMenuView); + mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig); + mMenuAnimator + .spring(DynamicAnimation.ALPHA, show ? 1f : 0f) + .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE) + .withEndActions(() -> { + mMenuAnimator = null; + endActions.run(); + }) + .start(); + } + + /** Cancel running animations */ + private void cancelAnimations() { + if (mMenuAnimator != null) { + mMenuAnimator.cancel(); + mMenuAnimator = null; + } + } + + /** Sets up and inflate menu views */ + private void setupMenu() { + // Menu view setup + mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate( + R.layout.bubble_bar_menu_view, mRootView, false); + mMenuView.setAlpha(0f); + mMenuView.setPivotY(0f); + mMenuView.setScaleY(MENU_INITIAL_SCALE); + mMenuView.setOnCloseListener(() -> hideMenu(true /* animated */)); + if (mBubble != null) { + mMenuView.updateInfo(mBubble); + mMenuView.updateActions(createMenuActions(mBubble)); + } + // Scrim view setup + mScrimView = new View(mContext); + mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + mScrimView.setOnClickListener(view -> hideMenu(true /* animated */)); + // Attach to root view + mRootView.addView(mScrimView); + mRootView.addView(mMenuView); + } + + /** + * Creates menu actions to populate menu view + * @param bubble used to create actions depending on bubble type + */ + private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) { + ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>(); + Resources resources = mContext.getResources(); + + if (bubble.isConversation()) { + // Don't bubble conversation action + menuActions.add(new BubbleBarMenuView.MenuAction( + Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble), + resources.getString(R.string.bubbles_dont_bubble_conversation), + view -> { + hideMenu(true /* animated */); + if (mListener != null) { + mListener.onUnBubbleConversation(bubble); + } + } + )); + // Open settings action + Icon appIcon = bubble.getRawAppBadge() != null ? Icon.createWithBitmap( + bubble.getRawAppBadge()) : null; + menuActions.add(new BubbleBarMenuView.MenuAction( + appIcon, + resources.getString(R.string.bubbles_app_settings, bubble.getAppName()), + view -> { + hideMenu(true /* animated */); + if (mListener != null) { + mListener.onOpenAppSettings(bubble); + } + } + )); + } + + // Dismiss bubble action + menuActions.add(new BubbleBarMenuView.MenuAction( + Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow), + resources.getString(R.string.bubble_dismiss_text), + ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close), + view -> { + hideMenu(true /* animated */); + if (mListener != null) { + mListener.onDismissBubble(bubble); + } + } + )); + + return menuActions; + } + + /** + * Bubble bar expanded view menu actions listener + */ + interface Listener { + /** + * Called when manage menu is shown/hidden + * If animated will be called when animation ends + */ + void onMenuVisibilityChanged(boolean visible); + + /** + * Un-bubbles conversation and removes the bubble from the stack + * This conversation will not be bubbled with new messages + * @see com.android.wm.shell.bubbles.BubbleController + */ + void onUnBubbleConversation(Bubble bubble); + + /** + * Launches app notification bubble settings for the bubble with intent created in: + * {@code Bubble.getSettingsIntent} + */ + void onOpenAppSettings(Bubble bubble); + + /** + * Dismiss bubble and remove it from the bubble stack + */ + void onDismissBubble(Bubble bubble); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt new file mode 100644 index 000000000000..ee552ae204b8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles.bar + +import android.annotation.LayoutRes +import android.content.Context +import android.graphics.Point +import android.graphics.Rect +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.core.view.doOnLayout +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringForce +import com.android.wm.shell.R +import com.android.wm.shell.animation.PhysicsAnimator +import com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION +import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES +import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME +import com.android.wm.shell.bubbles.BubbleEducationController +import com.android.wm.shell.bubbles.BubbleViewProvider +import com.android.wm.shell.bubbles.setup +import com.android.wm.shell.common.bubbles.BubblePopupDrawable +import com.android.wm.shell.common.bubbles.BubblePopupView +import kotlin.math.roundToInt + +/** Manages bubble education presentation and animation */ +class BubbleEducationViewController(private val context: Context, private val listener: Listener) { + interface Listener { + fun onEducationVisibilityChanged(isVisible: Boolean) + } + + private var rootView: ViewGroup? = null + private var educationView: BubblePopupView? = null + private var animator: PhysicsAnimator<BubblePopupView>? = null + + private val springConfig by lazy { + PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_MEDIUM, + SpringForce.DAMPING_RATIO_LOW_BOUNCY + ) + } + + private val scrimView by lazy { + View(context).apply { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + setOnClickListener { hideEducation(animated = true) } + } + } + + private val controller by lazy { BubbleEducationController(context) } + + /** Whether the education view is visible or being animated */ + val isEducationVisible: Boolean + get() = educationView != null && rootView != null + + /** + * Hide the current education view if visible + * + * @param animated whether should hide with animation + */ + @JvmOverloads + fun hideEducation(animated: Boolean, endActions: () -> Unit = {}) { + log { "hideEducation animated: $animated" } + + if (animated) { + animateTransition(show = false) { + cleanUp() + endActions() + listener.onEducationVisibilityChanged(isVisible = false) + } + } else { + cleanUp() + endActions() + listener.onEducationVisibilityChanged(isVisible = false) + } + } + + /** + * Show bubble bar stack user education. + * + * @param position the reference position for the user education in Screen coordinates. + * @param root the view to show user education in. + * @param educationClickHandler the on click handler for the user education view + */ + fun showStackEducation(position: Point, root: ViewGroup, educationClickHandler: () -> Unit) { + hideEducation(animated = false) + log { "showStackEducation at: $position" } + + educationView = + createEducationView(R.layout.bubble_bar_stack_education, root).apply { + setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN) + setArrowPosition(BubblePopupDrawable.ArrowPosition.End) + updateEducationPosition(view = this, position, root) + val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f + doOnLayout { + it.pivotX = it.width - arrowToEdgeOffset + it.pivotY = it.height.toFloat() + } + setOnClickListener { educationClickHandler() } + } + + rootView = root + animator = createAnimator() + + root.addView(scrimView) + root.addView(educationView) + animateTransition(show = true) { + controller.hasSeenStackEducation = true + listener.onEducationVisibilityChanged(isVisible = true) + } + } + + /** + * Show manage bubble education if hasn't been shown before + * + * @param bubble the bubble used for the manage education check + * @param root the view to show manage education in + */ + fun maybeShowManageEducation(bubble: BubbleViewProvider, root: ViewGroup) { + log { "maybeShowManageEducation bubble: $bubble" } + if (!controller.shouldShowManageEducation(bubble)) return + showManageEducation(root) + } + + /** + * Show manage education with animation + * + * @param root the view to show manage education in + */ + private fun showManageEducation(root: ViewGroup) { + hideEducation(animated = false) + log { "showManageEducation" } + + educationView = + createEducationView(R.layout.bubble_bar_manage_education, root).apply { + pivotY = 0f + doOnLayout { it.pivotX = it.width / 2f } + setOnClickListener { hideEducation(animated = true) } + } + + rootView = root + animator = createAnimator() + + root.addView(scrimView) + root.addView(educationView) + animateTransition(show = true) { + controller.hasSeenManageEducation = true + listener.onEducationVisibilityChanged(isVisible = true) + } + } + + /** + * Animate show/hide transition for the education view + * + * @param show whether to show or hide the view + * @param endActions a closure to be called when the animation completes + */ + private fun animateTransition(show: Boolean, endActions: () -> Unit) { + animator + ?.spring(DynamicAnimation.ALPHA, if (show) 1f else 0f) + ?.spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN) + ?.spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN) + ?.withEndActions(endActions) + ?.start() + ?: endActions() + } + + /** Remove education view from the root and clean up all relative properties */ + private fun cleanUp() { + log { "cleanUp" } + rootView?.removeView(educationView) + rootView?.removeView(scrimView) + educationView = null + rootView = null + animator = null + } + + /** + * Create education view by inflating layout provided. + * + * @param layout layout resource id to inflate. The root view should be [BubblePopupView] + * @param root view group to use as root for inflation, is not attached to root + */ + private fun createEducationView(@LayoutRes layout: Int, root: ViewGroup): BubblePopupView { + val view = LayoutInflater.from(context).inflate(layout, root, false) as BubblePopupView + view.setup() + view.alpha = 0f + view.scaleX = EDU_SCALE_HIDDEN + view.scaleY = EDU_SCALE_HIDDEN + return view + } + + /** Create animator for the user education transitions */ + private fun createAnimator(): PhysicsAnimator<BubblePopupView>? { + return educationView?.let { + PhysicsAnimator.getInstance(it).apply { setDefaultSpringConfig(springConfig) } + } + } + + /** + * Update user education view position relative to the reference position + * + * @param view the user education view to layout + * @param position the reference position in Screen coordinates + * @param root the root view to use for the layout + */ + private fun updateEducationPosition(view: BubblePopupView, position: Point, root: ViewGroup) { + val rootBounds = Rect() + // Get root bounds on screen as position is in screen coordinates + root.getBoundsOnScreen(rootBounds) + // Get the offset to the arrow from the edge of the education view + val arrowToEdgeOffset = + view.popupDrawable?.config?.let { it.cornerRadius + it.arrowWidth / 2f }?.roundToInt() + ?: 0 + // Calculate education view margins + val params = view.layoutParams as FrameLayout.LayoutParams + params.bottomMargin = rootBounds.bottom - position.y + params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset + view.layoutParams = params + } + + private fun log(msg: () -> String) { + if (DEBUG_USER_EDUCATION) Log.d(TAG, msg()) + } + + companion object { + private val TAG = if (TAG_WITH_CLASS_NAME) "BubbleEducationViewController" else TAG_BUBBLES + private const val EDU_SCALE_HIDDEN = 0.5f + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java deleted file mode 100644 index 9ee8a9d98aa1..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.wm.shell.bubbles.bar; - -import android.content.Context; -import android.view.Gravity; -import android.widget.LinearLayout; - -/** - * Handle / menu view to show at the top of a bubble bar expanded view. - */ -public class HandleView extends LinearLayout { - - // TODO(b/273307221): implement the manage menu in this view. - public HandleView(Context context) { - super(context); - setOrientation(LinearLayout.HORIZONTAL); - setGravity(Gravity.CENTER); - } - - /** - * The menu extends past the top of the TaskView because of the rounded corners. This means - * to center content in the menu we must subtract the radius (i.e. the amount of space covered - * by TaskView). - */ - public void setCornerRadius(float radius) { - setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius); - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt index bdfdad59c600..85aaa8ef585c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt @@ -14,14 +14,19 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.bubble +package com.android.wm.shell.bubbles.properties -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import org.junit.runner.RunWith -import org.junit.runners.Parameterized - -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class ChangeActiveActivityFromBubbleTestCfArm(flicker: FlickerTest) : - ChangeActiveActivityFromBubbleTest(flicker) +/** + * An interface for exposing bubble properties via flags which can be controlled easily in tests. + */ +interface BubbleProperties { + /** + * Whether bubble bar is enabled. + * + * When this is `true`, depending on additional factors, such as screen size and taskbar state, + * bubbles will be displayed in the bubble bar instead of floating. + * + * When this is `false`, bubbles will be floating. + */ + val isBubbleBarEnabled: Boolean +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt index 6c61710d6284..9d8b9a6f3260 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.bubble +package com.android.wm.shell.bubbles.properties -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import android.os.SystemProperties -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class OpenActivityFromBubbleTestCfArm(flicker: FlickerTest) : OpenActivityFromBubbleTest(flicker) +/** Provides bubble properties in production. */ +object ProdBubbleProperties : BubbleProperties { + + // TODO(b/256873975) Should use proper flag when available to shell/launcher + override val isBubbleBarEnabled = + SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java index 72702e7c2b88..b828aac39040 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java @@ -18,6 +18,7 @@ package com.android.wm.shell.common; import android.annotation.Nullable; import android.os.RemoteException; +import android.os.Trace; import android.util.Slog; import android.view.IDisplayChangeWindowCallback; import android.view.IDisplayChangeWindowController; @@ -40,6 +41,7 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public class DisplayChangeController { private static final String TAG = DisplayChangeController.class.getSimpleName(); + private static final String HANDLE_DISPLAY_CHANGE_TRACE_TAG = "HandleRemoteDisplayChange"; private final ShellExecutor mMainExecutor; private final IWindowManager mWmService; @@ -81,9 +83,15 @@ public class DisplayChangeController { /** Query all listeners for changes that should happen on display change. */ void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId, int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.beginSection("dispatchOnDisplayChange"); + } for (OnDisplayChangingListener c : mDisplayChangeListener) { c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct); } + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.endSection(); + } } private void onDisplayChange(int displayId, int fromRotation, int toRotation, @@ -94,6 +102,10 @@ public class DisplayChangeController { callback.continueDisplayChange(t); } catch (RemoteException e) { Slog.e(TAG, "Failed to continue handling display change", e); + } finally { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.endAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode()); + } } } @@ -103,6 +115,9 @@ public class DisplayChangeController { @Override public void onDisplayChange(int displayId, int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.beginAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode()); + } mMainExecutor.execute(() -> DisplayChangeController.this .onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, callback)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt index d5d072a8d449..122dcbb3c2ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt @@ -94,7 +94,6 @@ class FloatingContentCoordinator constructor() { * non-overlapping. * @return The new bounds for this content. */ - @JvmDefault fun calculateNewBoundsOnOverlap( overlappingContentBounds: Rect, otherContentBounds: List<Rect> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt new file mode 100644 index 000000000000..81592c35e4ac --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt @@ -0,0 +1,90 @@ +/* + * 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.common + +import android.window.WindowContainerToken +import android.window.WindowContainerTransaction +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG +import com.android.wm.shell.util.KtProtoLog + +/** + * Controller to manage behavior of activities launched with + * [android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT]. + */ +class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { + + /** Allows to temporarily disable launch adjacent handling */ + var launchAdjacentEnabled: Boolean = true + set(value) { + if (field != value) { + KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value) + field = value + container?.let { c -> + if (value) { + enableContainer(c) + } else { + disableContainer((c)) + } + } + } + } + private var container: WindowContainerToken? = null + + /** + * Set [container] as the new launch adjacent flag root container. + * + * If launch adjacent handling is disabled through [setLaunchAdjacentEnabled], won't set the + * container until after it is enabled again. + * + * @see WindowContainerTransaction.setLaunchAdjacentFlagRoot + */ + fun setLaunchAdjacentRoot(container: WindowContainerToken) { + KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container") + this.container = container + if (launchAdjacentEnabled) { + enableContainer(container) + } + } + + /** + * Clear a container previously set through [setLaunchAdjacentRoot]. + * + * Always clears the container, regardless of [launchAdjacentEnabled] value. + * + * @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot + */ + fun clearLaunchAdjacentRoot() { + KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container") + container?.let { + disableContainer(it) + container = null + } + } + + private fun enableContainer(container: WindowContainerToken) { + KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container") + val wct = WindowContainerTransaction() + wct.setLaunchAdjacentFlagRoot(container) + syncQueue.queue(wct) + } + + private fun disableContainer(container: WindowContainerToken) { + KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container") + val wct = WindowContainerTransaction() + wct.clearLaunchAdjacentFlagRoot(container) + syncQueue.queue(wct) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS new file mode 100644 index 000000000000..7af038999797 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS @@ -0,0 +1 @@ +madym@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java index 81423473171d..fc627a8dcb36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java @@ -35,6 +35,7 @@ public class BubbleBarUpdate implements Parcelable { public boolean expandedChanged; public boolean expanded; + public boolean shouldShowEducation; @Nullable public String selectedBubbleKey; @Nullable @@ -61,6 +62,7 @@ public class BubbleBarUpdate implements Parcelable { public BubbleBarUpdate(Parcel parcel) { expandedChanged = parcel.readBoolean(); expanded = parcel.readBoolean(); + shouldShowEducation = parcel.readBoolean(); selectedBubbleKey = parcel.readString(); addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(), BubbleInfo.class); @@ -95,6 +97,7 @@ public class BubbleBarUpdate implements Parcelable { return "BubbleBarUpdate{ expandedChanged=" + expandedChanged + " expanded=" + expanded + " selectedBubbleKey=" + selectedBubbleKey + + " shouldShowEducation=" + shouldShowEducation + " addedBubble=" + addedBubble + " updatedBubble=" + updatedBubble + " suppressedBubbleKey=" + suppressedBubbleKey @@ -114,6 +117,7 @@ public class BubbleBarUpdate implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeBoolean(expandedChanged); parcel.writeBoolean(expanded); + parcel.writeBoolean(shouldShowEducation); parcel.writeString(selectedBubbleKey); parcel.writeParcelable(addedBubble, flags); parcel.writeParcelable(updatedBubble, flags); diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java index e323ebf3b5c8..0329b8df7544 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.bubble +package com.android.wm.shell.common.bubbles; -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import org.junit.runner.RunWith -import org.junit.runners.Parameterized +/** + * Constants shared between bubbles in shell & things we have to do for bubbles in launcher. + */ +public class BubbleConstants { -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class SendBubbleNotificationTestCfArm(flicker: FlickerTest) : - SendBubbleNotificationTest(flicker) + /** The alpha for the scrim shown when bubbles are expanded. */ + public static float BUBBLE_EXPANDED_SCRIM_ALPHA = .32f; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java index 21355a3efa2e..24608d651d06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java @@ -129,6 +129,11 @@ public class BubbleInfo implements Parcelable { return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0; } + /** Sets the flags for this bubble. */ + public void setFlags(int flags) { + mFlags = flags; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt new file mode 100644 index 000000000000..887af17c9653 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt @@ -0,0 +1,232 @@ +/* + * 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.common.bubbles + +import android.annotation.ColorInt +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Matrix +import android.graphics.Outline +import android.graphics.Paint +import android.graphics.Path +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.drawable.Drawable +import kotlin.math.atan +import kotlin.math.cos +import kotlin.math.sin +import kotlin.properties.Delegates + +/** A drawable for the [BubblePopupView] that draws a popup background with a directional arrow */ +class BubblePopupDrawable(val config: Config) : Drawable() { + /** The direction of the arrow in the popup drawable */ + enum class ArrowDirection { + UP, + DOWN + } + + /** The arrow position on the side of the popup bubble */ + sealed class ArrowPosition { + object Start : ArrowPosition() + object Center : ArrowPosition() + object End : ArrowPosition() + class Custom(val value: Float) : ArrowPosition() + } + + /** The configuration for drawable features */ + data class Config( + @ColorInt val color: Int, + val cornerRadius: Float, + val contentPadding: Int, + val arrowWidth: Float, + val arrowHeight: Float, + val arrowRadius: Float + ) + + /** + * The direction of the arrow in the popup drawable. It affects the content padding and requires + * it to be updated in the view. + */ + var arrowDirection: ArrowDirection by + Delegates.observable(ArrowDirection.UP) { _, _, _ -> requestPathUpdate() } + + /** + * Arrow position along the X axis and its direction. The position is adjusted to the content + * corner radius when applied so it doesn't go into rounded corner area + */ + var arrowPosition: ArrowPosition by + Delegates.observable(ArrowPosition.Center) { _, _, _ -> requestPathUpdate() } + + private val path = Path() + private val paint = Paint() + private var shouldUpdatePath = true + + init { + paint.color = config.color + paint.style = Paint.Style.FILL + paint.isAntiAlias = true + } + + override fun draw(canvas: Canvas) { + updatePathIfNeeded() + canvas.drawPath(path, paint) + } + + override fun onBoundsChange(bounds: Rect) { + requestPathUpdate() + } + + /** Should be applied to the view padding if arrow direction changes */ + override fun getPadding(padding: Rect): Boolean { + padding.set( + config.contentPadding, + config.contentPadding, + config.contentPadding, + config.contentPadding + ) + when (arrowDirection) { + ArrowDirection.UP -> padding.top += config.arrowHeight.toInt() + ArrowDirection.DOWN -> padding.bottom += config.arrowHeight.toInt() + } + return true + } + + override fun getOutline(outline: Outline) { + updatePathIfNeeded() + outline.setPath(path) + } + + override fun getOpacity(): Int { + return paint.alpha + } + + override fun setAlpha(alpha: Int) { + paint.alpha = alpha + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + paint.colorFilter = colorFilter + } + + /** Schedules path update for the next redraw */ + private fun requestPathUpdate() { + shouldUpdatePath = true + } + + /** Updates the path if required, when bounds or arrow direction/position changes */ + private fun updatePathIfNeeded() { + if (shouldUpdatePath) { + updatePath() + shouldUpdatePath = false + } + } + + /** Updates the path value using the current bounds, config, arrow direction and position */ + private fun updatePath() { + if (bounds.isEmpty) return + // Reset the path state + path.reset() + // The content rect where the filled rounded rect will be drawn + val contentRect = RectF(bounds) + when (arrowDirection) { + ArrowDirection.UP -> { + // Add rounded arrow pointing up to the path + addRoundedArrowPositioned(path, arrowPosition) + // Inset content rect by the arrow size from the top + contentRect.top += config.arrowHeight + } + ArrowDirection.DOWN -> { + val matrix = Matrix() + // Flip the path with the matrix to draw arrow pointing down + matrix.setScale(1f, -1f, bounds.width() / 2f, bounds.height() / 2f) + path.transform(matrix) + // Add rounded arrow with the flipped matrix applied, will point down + addRoundedArrowPositioned(path, arrowPosition) + // Restore the path matrix to the original state with inverted matrix + matrix.invert(matrix) + path.transform(matrix) + // Inset content rect by the arrow size from the bottom + contentRect.bottom -= config.arrowHeight + } + } + // Add the content area rounded rect + path.addRoundRect(contentRect, config.cornerRadius, config.cornerRadius, Path.Direction.CW) + } + + /** Add a rounded arrow pointing up in the horizontal position on the canvas */ + private fun addRoundedArrowPositioned(path: Path, position: ArrowPosition) { + val matrix = Matrix() + var translationX = positionValue(position) - config.arrowWidth / 2 + // Offset to position between rounded corners of the content view + translationX = translationX.coerceIn(config.cornerRadius, + bounds.width() - config.cornerRadius - config.arrowWidth) + // Translate to add the arrow in the center horizontally + matrix.setTranslate(-translationX, 0f) + path.transform(matrix) + // Add rounded arrow + addRoundedArrow(path) + // Restore the path matrix to the original state with inverted matrix + matrix.invert(matrix) + path.transform(matrix) + } + + /** Adds a rounded arrow pointing up to the path, can be flipped if needed */ + private fun addRoundedArrow(path: Path) { + // Theta is half of the angle inside the triangle tip + val thetaTan = config.arrowWidth / (config.arrowHeight * 2f) + val theta = atan(thetaTan) + val thetaDeg = Math.toDegrees(theta.toDouble()).toFloat() + // The center Y value of the circle for the triangle tip + val tipCircleCenterY = config.arrowRadius / sin(theta) + // The length from triangle tip to intersection point with the circle + val tipIntersectionSideLength = config.arrowRadius / thetaTan + // The offset from the top to the point of intersection + val intersectionTopOffset = tipIntersectionSideLength * cos(theta) + // The offset from the center to the point of intersection + val intersectionCenterOffset = tipIntersectionSideLength * sin(theta) + // The center X of the triangle + val arrowCenterX = config.arrowWidth / 2f + + // Set initial position in bottom left of the arrow + path.moveTo(0f, config.arrowHeight) + // Add the left side of the triangle + path.lineTo(arrowCenterX - intersectionCenterOffset, intersectionTopOffset) + // Add the arc from the left to the right side of the triangle + path.arcTo( + /* left = */ arrowCenterX - config.arrowRadius, + /* top = */ tipCircleCenterY - config.arrowRadius, + /* right = */ arrowCenterX + config.arrowRadius, + /* bottom = */ tipCircleCenterY + config.arrowRadius, + /* startAngle = */ 180 + thetaDeg, + /* sweepAngle = */ 180 - (2 * thetaDeg), + /* forceMoveTo = */ false + ) + // Add the right side of the triangle + path.lineTo(config.arrowWidth, config.arrowHeight) + // Close the path + path.close() + } + + /** The value of the arrow position provided the position and current bounds */ + private fun positionValue(position: ArrowPosition): Float { + return when (position) { + is ArrowPosition.Start -> 0f + is ArrowPosition.Center -> bounds.width().toFloat() / 2f + is ArrowPosition.End -> bounds.width().toFloat() + is ArrowPosition.Custom -> position.value + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt new file mode 100644 index 000000000000..444fbf7884be --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt @@ -0,0 +1,66 @@ +/* + * 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.common.bubbles + +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import android.widget.LinearLayout + +/** A popup container view that uses [BubblePopupDrawable] as a background */ +open class BubblePopupView +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { + var popupDrawable: BubblePopupDrawable? = null + private set + + /** + * Sets up the popup drawable with the config provided. Required to remove dependency on local + * resources + */ + fun setupBackground(config: BubblePopupDrawable.Config) { + popupDrawable = BubblePopupDrawable(config) + background = popupDrawable + forceLayout() + } + + /** + * Sets the arrow direction for the background drawable and updates the padding to fit the + * content inside of the popup drawable + */ + fun setArrowDirection(direction: BubblePopupDrawable.ArrowDirection) { + popupDrawable?.let { + it.arrowDirection = direction + val padding = Rect() + if (it.getPadding(padding)) { + setPadding(padding.left, padding.top, padding.right, padding.bottom) + } + } + } + + /** Sets the arrow position for the background drawable and triggers redraw */ + fun setArrowPosition(position: BubblePopupDrawable.ArrowPosition) { + popupDrawable?.let { + it.arrowPosition = position + invalidate() + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java index e0c782d1675b..7c5bb211a4cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java @@ -14,16 +14,17 @@ * limitations under the License. */ -package com.android.wm.shell.common; +package com.android.wm.shell.common.bubbles; import android.content.Context; import android.content.res.Configuration; -import android.content.res.Resources; import android.view.Gravity; import android.widget.FrameLayout; import android.widget.ImageView; -import com.android.wm.shell.R; +import androidx.annotation.DimenRes; +import androidx.annotation.DrawableRes; +import androidx.core.content.ContextCompat; /** * Circular view with a semitransparent, circular background with an 'X' inside it. @@ -31,33 +32,44 @@ import com.android.wm.shell.R; * This is used by both Bubbles and PIP as the dismiss target. */ public class DismissCircleView extends FrameLayout { + @DrawableRes int mBackgroundResId; + @DimenRes int mIconSizeResId; private final ImageView mIconView = new ImageView(getContext()); public DismissCircleView(Context context) { super(context); - final Resources res = getResources(); - - setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); - - mIconView.setImageDrawable(res.getDrawable(R.drawable.pip_ic_close_white)); addView(mIconView); - - setViewSizes(); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - final Resources res = getResources(); - setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); + setBackground(ContextCompat.getDrawable(getContext(), mBackgroundResId)); + setViewSizes(); + } + + /** + * Sets up view with the provided resource ids. + * Decouples resource dependency in order to be used externally (e.g. Launcher) + * + * @param backgroundResId drawable resource id of the circle background + * @param iconResId drawable resource id of the icon for the dismiss view + * @param iconSizeResId dimen resource id of the icon size + */ + public void setup(@DrawableRes int backgroundResId, @DrawableRes int iconResId, + @DimenRes int iconSizeResId) { + mBackgroundResId = backgroundResId; + mIconSizeResId = iconSizeResId; + + setBackground(ContextCompat.getDrawable(getContext(), backgroundResId)); + mIconView.setImageDrawable(ContextCompat.getDrawable(getContext(), iconResId)); setViewSizes(); } /** Retrieves the current dimensions for the icon and circle and applies them. */ private void setViewSizes() { - final Resources res = getResources(); - final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size); + final int iconSize = getResources().getDimensionPixelSize(mIconSizeResId); mIconView.setLayoutParams( new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt index 67ecb915e098..2eb55e19a960 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt @@ -14,41 +14,75 @@ * limitations under the License. */ -package com.android.wm.shell.bubbles +package com.android.wm.shell.common.bubbles import android.animation.ObjectAnimator import android.content.Context import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.util.IntProperty +import android.util.Log import android.view.Gravity import android.view.View import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowManager import android.widget.FrameLayout +import androidx.annotation.ColorRes +import androidx.annotation.DimenRes +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW -import com.android.wm.shell.R import com.android.wm.shell.animation.PhysicsAnimator -import com.android.wm.shell.common.DismissCircleView -/* +/** * View that handles interactions between DismissCircleView and BubbleStackView. + * + * @note [setup] method should be called after initialisation */ class DismissView(context: Context) : FrameLayout(context) { + /** + * The configuration is used to provide module specific resource ids + * + * @see [setup] method + */ + data class Config( + /** The resource id to set on the dismiss target circle view */ + val dismissViewResId: Int, + /** dimen resource id of the dismiss target circle view size */ + @DimenRes val targetSizeResId: Int, + /** dimen resource id of the icon size in the dismiss target */ + @DimenRes val iconSizeResId: Int, + /** dimen resource id of the bottom margin for the dismiss target */ + @DimenRes var bottomMarginResId: Int, + /** dimen resource id of the height for dismiss area gradient */ + @DimenRes val floatingGradientHeightResId: Int, + /** color resource id of the dismiss area gradient color */ + @ColorRes val floatingGradientColorResId: Int, + /** drawable resource id of the dismiss target background */ + @DrawableRes val backgroundResId: Int, + /** drawable resource id of the icon for the dismiss target */ + @DrawableRes val iconResId: Int + ) + + companion object { + private const val SHOULD_SETUP = + "The view isn't ready. Should be called after `setup`" + private val TAG = DismissView::class.simpleName + } var circle = DismissCircleView(context) var isShowing = false - var targetSizeResId: Int + var config: Config? = null private val animator = PhysicsAnimator.getInstance(circle) private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) private val DISMISS_SCRIM_FADE_MS = 200L private var wm: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - private var gradientDrawable = createGradient() + private var gradientDrawable: GradientDrawable? = null private val GRADIENT_ALPHA: IntProperty<GradientDrawable> = object : IntProperty<GradientDrawable>("alpha") { @@ -61,23 +95,42 @@ class DismissView(context: Context) : FrameLayout(context) { } init { - setLayoutParams(LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), - Gravity.BOTTOM)) - updatePadding() setClipToPadding(false) setClipChildren(false) setVisibility(View.INVISIBLE) + addView(circle) + } + + /** + * Sets up view with the provided resource ids. + * + * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called + * with default params in module specific extension: + * @see [DismissView.setup] in DismissViewExt.kt + */ + fun setup(config: Config) { + this.config = config + + // Setup layout + layoutParams = LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + resources.getDimensionPixelSize(config.floatingGradientHeightResId), + Gravity.BOTTOM) + updatePadding() + + // Setup gradient + gradientDrawable = createGradient(color = config.floatingGradientColorResId) setBackgroundDrawable(gradientDrawable) - targetSizeResId = R.dimen.dismiss_circle_size - val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId) - addView(circle, LayoutParams(targetSize, targetSize, - Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)) - // start with circle offscreen so it's animated up - circle.setTranslationY(resources.getDimensionPixelSize( - R.dimen.floating_dismiss_gradient_height).toFloat()) + // Setup DismissCircleView + circle.id = config.dismissViewResId + circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId) + val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId) + circle.layoutParams = LayoutParams(targetSize, targetSize, + Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL) + // Initial position with circle offscreen so it's animated up + circle.translationY = resources.getDimensionPixelSize(config.floatingGradientHeightResId) + .toFloat() } /** @@ -85,6 +138,7 @@ class DismissView(context: Context) : FrameLayout(context) { */ fun show() { if (isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return isShowing = true setVisibility(View.VISIBLE) val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, @@ -104,6 +158,7 @@ class DismissView(context: Context) : FrameLayout(context) { */ fun hide() { if (!isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return isShowing = false val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 0) @@ -124,18 +179,17 @@ class DismissView(context: Context) : FrameLayout(context) { } fun updateResources() { + val config = checkExists(config) ?: return updatePadding() - layoutParams.height = resources.getDimensionPixelSize( - R.dimen.floating_dismiss_gradient_height) - - val targetSize = resources.getDimensionPixelSize(targetSizeResId) + layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId) + val targetSize = resources.getDimensionPixelSize(config.targetSizeResId) circle.layoutParams.width = targetSize circle.layoutParams.height = targetSize circle.requestLayout() } - private fun createGradient(): GradientDrawable { - val gradientColor = context.resources.getColor(android.R.color.system_neutral1_900) + private fun createGradient(@ColorRes color: Int): GradientDrawable { + val gradientColor = ContextCompat.getColor(context, color) val alpha = 0.7f * 255 val gradientColorWithAlpha = Color.argb(alpha.toInt(), Color.red(gradientColor), @@ -150,10 +204,22 @@ class DismissView(context: Context) : FrameLayout(context) { } private fun updatePadding() { + val config = checkExists(config) ?: return val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets() val navInset = insets.getInsetsIgnoringVisibility( WindowInsets.Type.navigationBars()) setPadding(0, 0, 0, navInset.bottom + - resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin)) + resources.getDimensionPixelSize(config.bottomMarginResId)) + } + + /** + * Checks if the value is set up and exists, if not logs an exception. + * Used for convenient logging in case `setup` wasn't called before + * + * @return value provided as argument + */ + private fun <T>checkExists(value: T?): T? { + if (value == null) Log.e(TAG, SHOULD_SETUP) + return value } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt index ea9d065d5f53..cc37bd3a4589 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.bubbles +package com.android.wm.shell.common.bubbles import android.graphics.PointF import android.view.MotionEvent diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt new file mode 100644 index 000000000000..a8743fbed5e0 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt @@ -0,0 +1,201 @@ +/* + * 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.common.pip + +import android.content.Context +import android.content.res.Resources +import android.graphics.PointF +import android.util.Size +import com.android.wm.shell.R + +class LegacySizeSpecSource( + private val context: Context, + private val pipDisplayLayoutState: PipDisplayLayoutState +) : SizeSpecSource { + + private var mDefaultMinSize = 0 + /** The absolute minimum an overridden size's edge can be */ + private var mOverridableMinSize = 0 + /** The preferred minimum (and default minimum) size specified by apps. */ + private var mOverrideMinSize: Size? = null + + private var mDefaultSizePercent = 0f + private var mMinimumSizePercent = 0f + private var mMaxAspectRatioForMinSize = 0f + private var mMinAspectRatioForMinSize = 0f + + init { + reloadResources() + } + + private fun reloadResources() { + val res: Resources = context.getResources() + + mDefaultMinSize = res.getDimensionPixelSize( + R.dimen.default_minimal_size_pip_resizable_task) + mOverridableMinSize = res.getDimensionPixelSize( + R.dimen.overridable_minimal_size_pip_resizable_task) + + mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent) + mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1) + + mMaxAspectRatioForMinSize = res.getFloat( + R.dimen.config_pictureInPictureAspectRatioLimitForMinSize) + mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize + } + + override fun onConfigurationChanged() { + reloadResources() + } + + override fun getMaxSize(aspectRatio: Float): Size { + val insetBounds = pipDisplayLayoutState.insetBounds + + val shorterLength: Int = Math.min(getDisplayBounds().width(), + getDisplayBounds().height()) + val totalHorizontalPadding: Int = (insetBounds.left + + (getDisplayBounds().width() - insetBounds.right)) + val totalVerticalPadding: Int = (insetBounds.top + + (getDisplayBounds().height() - insetBounds.bottom)) + + return if (aspectRatio > 1f) { + val maxWidth = Math.max(getDefaultSize(aspectRatio).width, + shorterLength - totalHorizontalPadding) + val maxHeight = (maxWidth / aspectRatio).toInt() + Size(maxWidth, maxHeight) + } else { + val maxHeight = Math.max(getDefaultSize(aspectRatio).height, + shorterLength - totalVerticalPadding) + val maxWidth = (maxHeight * aspectRatio).toInt() + Size(maxWidth, maxHeight) + } + } + + override fun getDefaultSize(aspectRatio: Float): Size { + if (mOverrideMinSize != null) { + return getMinSize(aspectRatio) + } + val smallestDisplaySize: Int = Math.min(getDisplayBounds().width(), + getDisplayBounds().height()) + val minSize = Math.max(getMinEdgeSize().toFloat(), + smallestDisplaySize * mDefaultSizePercent).toInt() + val width: Int + val height: Int + if (aspectRatio <= mMinAspectRatioForMinSize || + aspectRatio > mMaxAspectRatioForMinSize) { + // Beyond these points, we can just use the min size as the shorter edge + if (aspectRatio <= 1) { + // Portrait, width is the minimum size + width = minSize + height = Math.round(width / aspectRatio) + } else { + // Landscape, height is the minimum size + height = minSize + width = Math.round(height * aspectRatio) + } + } else { + // Within these points, ensure that the bounds fit within the radius of the limits + // at the points + val widthAtMaxAspectRatioForMinSize: Float = mMaxAspectRatioForMinSize * minSize + val radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize.toFloat()) + height = Math.round(Math.sqrt((radius * radius / + (aspectRatio * aspectRatio + 1)).toDouble())).toInt() + width = Math.round(height * aspectRatio) + } + return Size(width, height) + } + + override fun getMinSize(aspectRatio: Float): Size { + if (mOverrideMinSize != null) { + return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! + } + val shorterLength: Int = Math.min(getDisplayBounds().width(), + getDisplayBounds().height()) + val minWidth: Int + val minHeight: Int + if (aspectRatio > 1f) { + minWidth = Math.min(getDefaultSize(aspectRatio).width.toFloat(), + shorterLength * mMinimumSizePercent).toInt() + minHeight = (minWidth / aspectRatio).toInt() + } else { + minHeight = Math.min(getDefaultSize(aspectRatio).height.toFloat(), + shorterLength * mMinimumSizePercent).toInt() + minWidth = (minHeight * aspectRatio).toInt() + } + return Size(minWidth, minHeight) + } + + override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size { + val smallestSize = Math.min(size.width, size.height) + val minSize = Math.max(getMinEdgeSize(), smallestSize) + val width: Int + val height: Int + if (aspectRatio <= 1) { + // Portrait, width is the minimum size. + width = minSize + height = Math.round(width / aspectRatio) + } else { + // Landscape, height is the minimum size + height = minSize + width = Math.round(height * aspectRatio) + } + return Size(width, height) + } + + private fun getDisplayBounds() = pipDisplayLayoutState.displayBounds + + /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ + override fun setOverrideMinSize(overrideMinSize: Size?) { + mOverrideMinSize = overrideMinSize + } + + /** Returns the preferred minimal size specified by the activity in PIP. */ + override fun getOverrideMinSize(): Size? { + val overrideMinSize = mOverrideMinSize ?: return null + return if (overrideMinSize.width < mOverridableMinSize || + overrideMinSize.height < mOverridableMinSize) { + Size(mOverridableMinSize, mOverridableMinSize) + } else { + overrideMinSize + } + } + + private fun getMinEdgeSize(): Int { + return if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize() + } + + /** + * Returns the adjusted overridden min size if it is set; otherwise, returns null. + * + * + * Overridden min size needs to be adjusted in its own way while making sure that the target + * aspect ratio is maintained + * + * @param aspectRatio target aspect ratio + */ + private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? { + val size = getOverrideMinSize() ?: return null + val sizeAspectRatio = size.width / size.height.toFloat() + return if (sizeAspectRatio > aspectRatio) { + // Size is wider, fix the width and increase the height + Size(size.width, (size.width / aspectRatio).toInt()) + } else { + // Size is taller, fix the height and adjust the width. + Size((size.height * aspectRatio).toInt(), size.height) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java index f9332e4bdb2e..133242d15822 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip.phone; +package com.android.wm.shell.common.pip; import android.content.Context; import android.content.res.Resources; @@ -24,9 +24,6 @@ import android.util.ArraySet; import android.view.Gravity; import com.android.wm.shell.R; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; import java.util.Set; @@ -40,6 +37,7 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac "persist.wm.debug.enable_pip_keep_clear_algorithm_gravity", false); protected int mKeepClearAreasPadding; + private int mImeOffset; public PhonePipKeepClearAlgorithm(Context context) { reloadResources(context); @@ -48,6 +46,7 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac private void reloadResources(Context context) { final Resources res = context.getResources(); mKeepClearAreasPadding = res.getDimensionPixelSize(R.dimen.pip_keep_clear_areas_padding); + mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset); } /** @@ -61,7 +60,7 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac Rect insets = new Rect(); pipBoundsAlgorithm.getInsetBounds(insets); if (pipBoundsState.isImeShowing()) { - insets.bottom -= pipBoundsState.getImeHeight(); + insets.bottom -= (pipBoundsState.getImeHeight() + mImeOffset); } // if PiP is stashed we only adjust the vertical position if it's outside of insets and // ignore all keep clear areas, since it's already on the side diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt new file mode 100644 index 000000000000..18c7bdd6d5ba --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt @@ -0,0 +1,251 @@ +/* + * 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.common.pip + +import android.content.Context +import android.content.res.Resources +import android.os.SystemProperties +import android.util.Size +import com.android.wm.shell.R +import java.io.PrintWriter + +class PhoneSizeSpecSource( + private val context: Context, + private val pipDisplayLayoutState: PipDisplayLayoutState +) : SizeSpecSource { + private var DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16 + + private var mDefaultMinSize = 0 + /** The absolute minimum an overridden size's edge can be */ + private var mOverridableMinSize = 0 + /** The preferred minimum (and default minimum) size specified by apps. */ + private var mOverrideMinSize: Size? = null + + + /** Default and minimum percentages for the PIP size logic. */ + private val mDefaultSizePercent: Float + private val mMinimumSizePercent: Float + + /** Aspect ratio that the PIP size spec logic optimizes for. */ + private var mOptimizedAspectRatio = 0f + + init { + mDefaultSizePercent = SystemProperties + .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat() + mMinimumSizePercent = SystemProperties + .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat() + + reloadResources() + } + + private fun reloadResources() { + val res: Resources = context.getResources() + + mDefaultMinSize = res.getDimensionPixelSize( + R.dimen.default_minimal_size_pip_resizable_task) + mOverridableMinSize = res.getDimensionPixelSize( + R.dimen.overridable_minimal_size_pip_resizable_task) + + val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio) + // make sure the optimized aspect ratio is valid with a default value to fall back to + mOptimizedAspectRatio = if (requestedOptAspRatio > 1) { + DEFAULT_OPTIMIZED_ASPECT_RATIO + } else { + requestedOptAspRatio + } + } + + override fun onConfigurationChanged() { + reloadResources() + } + + /** + * Calculates the max size of PIP. + * + * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge. + * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the + * whole screen. A linear function is used to calculate these sizes. + * + * @param aspectRatio aspect ratio of the PIP window + * @return dimensions of the max size of the PIP + */ + override fun getMaxSize(aspectRatio: Float): Size { + val insetBounds = pipDisplayLayoutState.insetBounds + val displayBounds = pipDisplayLayoutState.displayBounds + + val totalHorizontalPadding: Int = (insetBounds.left + + (displayBounds.width() - insetBounds.right)) + val totalVerticalPadding: Int = (insetBounds.top + + (displayBounds.height() - insetBounds.bottom)) + val shorterLength: Int = Math.min(displayBounds.width() - totalHorizontalPadding, + displayBounds.height() - totalVerticalPadding) + var maxWidth: Int + val maxHeight: Int + + // use the optimized max sizing logic only within a certain aspect ratio range + if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) { + // this formula and its derivation is explained in b/198643358#comment16 + maxWidth = Math.round(mOptimizedAspectRatio * shorterLength + + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 + aspectRatio)) + // make sure the max width doesn't go beyond shorter screen length after rounding + maxWidth = Math.min(maxWidth, shorterLength) + maxHeight = Math.round(maxWidth / aspectRatio) + } else { + if (aspectRatio > 1f) { + maxWidth = shorterLength + maxHeight = Math.round(maxWidth / aspectRatio) + } else { + maxHeight = shorterLength + maxWidth = Math.round(maxHeight * aspectRatio) + } + } + return Size(maxWidth, maxHeight) + } + + /** + * Decreases the dimensions by a percentage relative to max size to get default size. + * + * @param aspectRatio aspect ratio of the PIP window + * @return dimensions of the default size of the PIP + */ + override fun getDefaultSize(aspectRatio: Float): Size { + val minSize = getMinSize(aspectRatio) + if (mOverrideMinSize != null) { + return minSize + } + val maxSize = getMaxSize(aspectRatio) + val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent), + minSize.width) + val defaultHeight = Math.round(defaultWidth / aspectRatio) + return Size(defaultWidth, defaultHeight) + } + + /** + * Decreases the dimensions by a certain percentage relative to max size to get min size. + * + * @param aspectRatio aspect ratio of the PIP window + * @return dimensions of the min size of the PIP + */ + override fun getMinSize(aspectRatio: Float): Size { + // if there is an overridden min size provided, return that + if (mOverrideMinSize != null) { + return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! + } + val maxSize = getMaxSize(aspectRatio) + var minWidth = Math.round(maxSize.width * mMinimumSizePercent) + var minHeight = Math.round(maxSize.height * mMinimumSizePercent) + + // make sure the calculated min size is not smaller than the allowed default min size + if (aspectRatio > 1f) { + minHeight = Math.max(minHeight, mDefaultMinSize) + minWidth = Math.round(minHeight * aspectRatio) + } else { + minWidth = Math.max(minWidth, mDefaultMinSize) + minHeight = Math.round(minWidth / aspectRatio) + } + return Size(minWidth, minHeight) + } + + /** + * Returns the size for target aspect ratio making sure new size conforms with the rules. + * + * + * Recalculates the dimensions such that the target aspect ratio is achieved, while + * maintaining the same maximum size to current size ratio. + * + * @param size current size + * @param aspectRatio target aspect ratio + */ + override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size { + if (size == mOverrideMinSize) { + return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! + } + + val currAspectRatio = size.width.toFloat() / size.height + + // getting the percentage of the max size that current size takes + val currentMaxSize = getMaxSize(currAspectRatio) + val currentPercent = size.width.toFloat() / currentMaxSize.width + + // getting the max size for the target aspect ratio + val updatedMaxSize = getMaxSize(aspectRatio) + var width = Math.round(updatedMaxSize.width * currentPercent) + var height = Math.round(updatedMaxSize.height * currentPercent) + + // adjust the dimensions if below allowed min edge size + val minEdgeSize = + if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize() + + if (width < minEdgeSize && aspectRatio <= 1) { + width = minEdgeSize + height = Math.round(width / aspectRatio) + } else if (height < minEdgeSize && aspectRatio > 1) { + height = minEdgeSize + width = Math.round(height * aspectRatio) + } + + // reduce the dimensions of the updated size to the calculated percentage + return Size(width, height) + } + + /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ + override fun setOverrideMinSize(overrideMinSize: Size?) { + mOverrideMinSize = overrideMinSize + } + + /** Returns the preferred minimal size specified by the activity in PIP. */ + override fun getOverrideMinSize(): Size? { + val overrideMinSize = mOverrideMinSize ?: return null + return if (overrideMinSize.width < mOverridableMinSize || + overrideMinSize.height < mOverridableMinSize) { + Size(mOverridableMinSize, mOverridableMinSize) + } else { + overrideMinSize + } + } + + /** + * Returns the adjusted overridden min size if it is set; otherwise, returns null. + * + * + * Overridden min size needs to be adjusted in its own way while making sure that the target + * aspect ratio is maintained + * + * @param aspectRatio target aspect ratio + */ + private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? { + val size = getOverrideMinSize() ?: return null + val sizeAspectRatio = size.width / size.height.toFloat() + return if (sizeAspectRatio > aspectRatio) { + // Size is wider, fix the width and increase the height + Size(size.width, (size.width / aspectRatio).toInt()) + } else { + // Size is taller, fix the height and adjust the width. + Size((size.height * aspectRatio).toInt(), size.height) + } + } + + override fun dump(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize) + pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize) + pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize) + pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent) + pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent) + pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt new file mode 100644 index 000000000000..4abb35c2a428 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt @@ -0,0 +1,78 @@ +/* + * 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.common.pip + +import android.app.AppOpsManager +import android.content.Context +import android.content.pm.PackageManager +import com.android.wm.shell.common.ShellExecutor + +class PipAppOpsListener( + private val mContext: Context, + private val mCallback: Callback, + private val mMainExecutor: ShellExecutor +) { + private val mAppOpsManager: AppOpsManager = checkNotNull( + mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager) + private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName -> + try { + // Dismiss the PiP once the user disables the app ops setting for that package + val topPipActivityInfo = PipUtils.getTopPipActivity(mContext) + val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener + val userId = topPipActivityInfo.second + val appInfo = mContext.packageManager + .getApplicationInfoAsUser(packageName, 0, userId) + if (appInfo.packageName == componentName.packageName && + mAppOpsManager.checkOpNoThrow( + AppOpsManager.OP_PICTURE_IN_PICTURE, appInfo.uid, + packageName + ) != AppOpsManager.MODE_ALLOWED + ) { + mMainExecutor.execute { mCallback.dismissPip() } + } + } catch (e: PackageManager.NameNotFoundException) { + // Unregister the listener if the package can't be found + unregisterAppOpsListener() + } + } + + fun onActivityPinned(packageName: String) { + // Register for changes to the app ops setting for this package while it is in PiP + registerAppOpsListener(packageName) + } + + fun onActivityUnpinned() { + // Unregister for changes to the previously PiP'ed package + unregisterAppOpsListener() + } + + private fun registerAppOpsListener(packageName: String) { + mAppOpsManager.startWatchingMode( + AppOpsManager.OP_PICTURE_IN_PICTURE, packageName, + mAppOpsChangedListener + ) + } + + private fun unregisterAppOpsListener() { + mAppOpsManager.stopWatchingMode(mAppOpsChangedListener) + } + + /** Callback for PipAppOpsListener to request changes to the PIP window. */ + interface Callback { + /** Dismisses the PIP window. */ + fun dismissPip() + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java index f51eb5299dd9..a9f687fc9b2d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip; +package com.android.wm.shell.common.pip; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,7 +28,6 @@ import android.util.Size; import android.view.Gravity; import com.android.wm.shell.R; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import java.io.PrintWriter; @@ -41,7 +40,8 @@ public class PipBoundsAlgorithm { private static final float INVALID_SNAP_FRACTION = -1f; @NonNull private final PipBoundsState mPipBoundsState; - @NonNull protected final PipSizeSpecHandler mPipSizeSpecHandler; + @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState; + @NonNull protected final SizeSpecSource mSizeSpecSource; private final PipSnapAlgorithm mSnapAlgorithm; private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm; @@ -53,11 +53,13 @@ public class PipBoundsAlgorithm { public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState, @NonNull PipSnapAlgorithm pipSnapAlgorithm, @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, - @NonNull PipSizeSpecHandler pipSizeSpecHandler) { + @NonNull PipDisplayLayoutState pipDisplayLayoutState, + @NonNull SizeSpecSource sizeSpecSource) { mPipBoundsState = pipBoundsState; mSnapAlgorithm = pipSnapAlgorithm; mPipKeepClearAlgorithm = pipKeepClearAlgorithm; - mPipSizeSpecHandler = pipSizeSpecHandler; + mPipDisplayLayoutState = pipDisplayLayoutState; + mSizeSpecSource = sizeSpecSource; reloadResources(context); // Initialize the aspect ratio to the default aspect ratio. Don't do this in reload // resources as it would clobber mAspectRatio when entering PiP from fullscreen which @@ -74,11 +76,6 @@ public class PipBoundsAlgorithm { R.dimen.config_pictureInPictureDefaultAspectRatio); mDefaultStackGravity = res.getInteger( R.integer.config_defaultPictureInPictureGravity); - final String screenEdgeInsetsDpString = res.getString( - R.string.config_defaultPictureInPictureScreenEdgeInsets); - final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() - ? Size.parseSize(screenEdgeInsetsDpString) - : null; mMinAspectRatio = res.getFloat( com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio); mMaxAspectRatio = res.getFloat( @@ -160,8 +157,8 @@ public class PipBoundsAlgorithm { // If either dimension is smaller than the allowed minimum, adjust them // according to mOverridableMinSize return new Size( - Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()), - Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize())); + Math.max(windowLayout.minWidth, getOverrideMinEdgeSize()), + Math.max(windowLayout.minHeight, getOverrideMinEdgeSize())); } return null; } @@ -203,7 +200,8 @@ public class PipBoundsAlgorithm { * * @return {@code false} if the given source is too small to use for the entering animation. */ - static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) { + public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, + Rect destinationBounds) { return sourceRectHint != null && sourceRectHint.width() > destinationBounds.width() && sourceRectHint.height() > destinationBounds.height(); @@ -225,7 +223,7 @@ public class PipBoundsAlgorithm { } /** - * @return whether the given {@param aspectRatio} is valid. + * @return whether the given aspectRatio is valid. */ public boolean isValidPictureInPictureAspectRatio(float aspectRatio) { return Float.compare(mMinAspectRatio, aspectRatio) <= 0 @@ -255,10 +253,10 @@ public class PipBoundsAlgorithm { final Size size; if (useCurrentMinEdgeSize || useCurrentSize) { // Use the existing size but adjusted to the new aspect ratio. - size = mPipSizeSpecHandler.getSizeForAspectRatio( + size = mSizeSpecSource.getSizeForAspectRatio( new Size(stackBounds.width(), stackBounds.height()), aspectRatio); } else { - size = mPipSizeSpecHandler.getDefaultSize(aspectRatio); + size = mSizeSpecSource.getDefaultSize(aspectRatio); } final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f); @@ -287,7 +285,7 @@ public class PipBoundsAlgorithm { getInsetBounds(insetBounds); // Calculate the default size - defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio); + defaultSize = mSizeSpecSource.getDefaultSize(mDefaultAspectRatio); // Now that we have the default size, apply the snap fraction if valid or position the // bounds using the default gravity. @@ -309,7 +307,11 @@ public class PipBoundsAlgorithm { * Populates the bounds on the screen that the PIP can be visible in. */ public void getInsetBounds(Rect outRect) { - outRect.set(mPipSizeSpecHandler.getInsetBounds()); + outRect.set(mPipDisplayLayoutState.getInsetBounds()); + } + + private int getOverrideMinEdgeSize() { + return mSizeSpecSource.getOverrideMinEdgeSize(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 9a775dff1f69..3b32b6c7b083 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip; +package com.android.wm.shell.common.pip; import android.annotation.IntDef; import android.annotation.NonNull; @@ -36,7 +36,6 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.function.TriConsumer; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; @@ -87,7 +86,7 @@ public class PipBoundsState { private int mStashOffset; private @Nullable PipReentryState mPipReentryState; private final LauncherState mLauncherState = new LauncherState(); - private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler; + private final @NonNull SizeSpecSource mSizeSpecSource; private @Nullable ComponentName mLastPipComponentName; private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState(); private boolean mIsImeShowing; @@ -127,17 +126,20 @@ public class PipBoundsState { private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); - public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler, - PipDisplayLayoutState pipDisplayLayoutState) { + public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource, + @NonNull PipDisplayLayoutState pipDisplayLayoutState) { mContext = context; reloadResources(); - mPipSizeSpecHandler = pipSizeSpecHandler; + mSizeSpecSource = sizeSpecSource; mPipDisplayLayoutState = pipDisplayLayoutState; } /** Reloads the resources. */ public void onConfigurationChanged() { reloadResources(); + + // update the size spec resources upon config change too + mSizeSpecSource.onConfigurationChanged(); } private void reloadResources() { @@ -311,15 +313,18 @@ public class PipBoundsState { return mPipDisplayLayoutState.getDisplayLayout(); } + /** + * Clears the PiP re-entry state. + */ @VisibleForTesting - void clearReentryState() { + public void clearReentryState() { mPipReentryState = null; } /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ public void setOverrideMinSize(@Nullable Size overrideMinSize) { final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize()); - mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize); + mSizeSpecSource.setOverrideMinSize(overrideMinSize); if (changed && mOnMinimalSizeChangeCallback != null) { mOnMinimalSizeChangeCallback.run(); } @@ -328,12 +333,12 @@ public class PipBoundsState { /** Returns the preferred minimal size specified by the activity in PIP. */ @Nullable public Size getOverrideMinSize() { - return mPipSizeSpecHandler.getOverrideMinSize(); + return mSizeSpecSource.getOverrideMinSize(); } /** Returns the minimum edge size of the override minimum size, or 0 if not set. */ public int getOverrideMinEdgeSize() { - return mPipSizeSpecHandler.getOverrideMinEdgeSize(); + return mSizeSpecSource.getOverrideMinEdgeSize(); } /** Get the state of the bounds in motion. */ @@ -397,11 +402,18 @@ public class PipBoundsState { mNamedUnrestrictedKeepClearAreas.remove(name); } + + /** + * @return restricted keep clear areas. + */ @NonNull public Set<Rect> getRestrictedKeepClearAreas() { return mRestrictedKeepClearAreas; } + /** + * @return unrestricted keep clear areas. + */ @NonNull public Set<Rect> getUnrestrictedKeepClearAreas() { if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas; @@ -558,7 +570,11 @@ public class PipBoundsState { } } - static final class PipReentryState { + /** + * Represents the state of pip to potentially restore upon reentry. + */ + @VisibleForTesting + public static final class PipReentryState { private static final String TAG = PipReentryState.class.getSimpleName(); private final @Nullable Size mSize; @@ -570,11 +586,11 @@ public class PipBoundsState { } @Nullable - Size getSize() { + public Size getSize() { return mSize; } - float getSnapFraction() { + public float getSnapFraction() { return mSnapFraction; } @@ -613,5 +629,6 @@ public class PipBoundsState { } mLauncherState.dump(pw, innerPrefix); mMotionBoundsState.dump(pw, innerPrefix); + mSizeSpecSource.dump(pw, innerPrefix); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java index 0f76af48199f..ed42117a55af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java @@ -14,14 +14,20 @@ * limitations under the License. */ -package com.android.wm.shell.pip; +package com.android.wm.shell.common.pip; + +import static com.android.wm.shell.common.pip.PipUtils.dpToPx; import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; import android.graphics.Rect; +import android.util.Size; import android.view.Surface; import androidx.annotation.NonNull; +import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.dagger.WMSingleton; @@ -40,13 +46,51 @@ public class PipDisplayLayoutState { private int mDisplayId; @NonNull private DisplayLayout mDisplayLayout; + private Point mScreenEdgeInsets = null; + @Inject public PipDisplayLayoutState(Context context) { mContext = context; mDisplayLayout = new DisplayLayout(); + reloadResources(); + } + + /** Responds to configuration change. */ + public void onConfigurationChanged() { + reloadResources(); + } + + private void reloadResources() { + Resources res = mContext.getResources(); + + final String screenEdgeInsetsDpString = res.getString( + R.string.config_defaultPictureInPictureScreenEdgeInsets); + final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() + ? Size.parseSize(screenEdgeInsetsDpString) + : null; + mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point() + : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()), + dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics())); + } + + public Point getScreenEdgeInsets() { + return mScreenEdgeInsets; + } + + /** + * Returns the inset bounds the PIP window can be visible in. + */ + public Rect getInsetBounds() { + Rect insetBounds = new Rect(); + Rect insets = getDisplayLayout().stableInsets(); + insetBounds.set(insets.left + getScreenEdgeInsets().x, + insets.top + getScreenEdgeInsets().y, + getDisplayLayout().width() - insets.right - getScreenEdgeInsets().x, + getDisplayLayout().height() - insets.bottom - getScreenEdgeInsets().y); + return insetBounds; } - /** Update the display layout. */ + /** Set the display layout. */ public void setDisplayLayout(@NonNull DisplayLayout displayLayout) { mDisplayLayout.set(displayLayout); } @@ -87,5 +131,6 @@ public class PipDisplayLayoutState { pw.println(prefix + TAG); pw.println(innerPrefix + "mDisplayId=" + mDisplayId); pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds()); + pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java index 5045cf905ee6..954233c04ed4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip; +package com.android.wm.shell.common.pip; import android.graphics.Rect; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt new file mode 100644 index 000000000000..427a555eee92 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt @@ -0,0 +1,364 @@ +/* + * 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.common.pip + +import android.annotation.DrawableRes +import android.annotation.StringRes +import android.app.PendingIntent +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.MediaController +import android.media.session.MediaSession +import android.media.session.MediaSessionManager +import android.media.session.PlaybackState +import android.os.Handler +import android.os.HandlerExecutor +import android.os.UserHandle +import com.android.wm.shell.R +import java.util.function.Consumer + +/** + * Interfaces with the [MediaSessionManager] to compose the right set of actions to show (only + * if there are no actions from the PiP activity itself). The active media controller is only set + * when there is a media session from the top PiP activity. + */ +class PipMediaController(private val mContext: Context, private val mMainHandler: Handler) { + /** + * A listener interface to receive notification on changes to the media actions. + */ + interface ActionListener { + /** + * Called when the media actions changed. + */ + fun onMediaActionsChanged(actions: List<RemoteAction?>?) + } + + /** + * A listener interface to receive notification on changes to the media metadata. + */ + interface MetadataListener { + /** + * Called when the media metadata changed. + */ + fun onMediaMetadataChanged(metadata: MediaMetadata?) + } + + /** + * A listener interface to receive notification on changes to the media session token. + */ + interface TokenListener { + /** + * Called when the media session token changed. + */ + fun onMediaSessionTokenChanged(token: MediaSession.Token?) + } + + private val mHandlerExecutor: HandlerExecutor = HandlerExecutor(mMainHandler) + private val mMediaSessionManager: MediaSessionManager? + private var mMediaController: MediaController? = null + private val mPauseAction: RemoteAction + private val mPlayAction: RemoteAction + private val mNextAction: RemoteAction + private val mPrevAction: RemoteAction + private val mMediaActionReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (mMediaController == null) { + // no active media session, bail early. + return + } + when (intent.action) { + ACTION_PLAY -> mMediaController!!.transportControls.play() + ACTION_PAUSE -> mMediaController!!.transportControls.pause() + ACTION_NEXT -> mMediaController!!.transportControls.skipToNext() + ACTION_PREV -> mMediaController!!.transportControls.skipToPrevious() + } + } + } + private val mPlaybackChangedListener: MediaController.Callback = + object : MediaController.Callback() { + override fun onPlaybackStateChanged(state: PlaybackState?) { + notifyActionsChanged() + } + + override fun onMetadataChanged(metadata: MediaMetadata?) { + notifyMetadataChanged(metadata) + } + } + private val mSessionsChangedListener = + MediaSessionManager.OnActiveSessionsChangedListener { controllers: List<MediaController>? -> + resolveActiveMediaController(controllers) + } + private val mActionListeners = ArrayList<ActionListener>() + private val mMetadataListeners = ArrayList<MetadataListener>() + private val mTokenListeners = ArrayList<TokenListener>() + + init { + val mediaControlFilter = IntentFilter() + mediaControlFilter.addAction(ACTION_PLAY) + mediaControlFilter.addAction(ACTION_PAUSE) + mediaControlFilter.addAction(ACTION_NEXT) + mediaControlFilter.addAction(ACTION_PREV) + mContext.registerReceiverForAllUsers( + mMediaActionReceiver, mediaControlFilter, + SYSTEMUI_PERMISSION, mMainHandler, Context.RECEIVER_EXPORTED + ) + + // Creates the standard media buttons that we may show. + mPauseAction = getDefaultRemoteAction( + R.string.pip_pause, + R.drawable.pip_ic_pause_white, ACTION_PAUSE + ) + mPlayAction = getDefaultRemoteAction( + R.string.pip_play, + R.drawable.pip_ic_play_arrow_white, ACTION_PLAY + ) + mNextAction = getDefaultRemoteAction( + R.string.pip_skip_to_next, + R.drawable.pip_ic_skip_next_white, ACTION_NEXT + ) + mPrevAction = getDefaultRemoteAction( + R.string.pip_skip_to_prev, + R.drawable.pip_ic_skip_previous_white, ACTION_PREV + ) + mMediaSessionManager = mContext.getSystemService( + MediaSessionManager::class.java + ) + } + + /** + * Handles when an activity is pinned. + */ + fun onActivityPinned() { + // Once we enter PiP, try to find the active media controller for the top most activity + resolveActiveMediaController( + mMediaSessionManager!!.getActiveSessionsForUser( + null, + UserHandle.CURRENT + ) + ) + } + + /** + * Adds a new media action listener. + */ + fun addActionListener(listener: ActionListener) { + if (!mActionListeners.contains(listener)) { + mActionListeners.add(listener) + listener.onMediaActionsChanged(mediaActions) + } + } + + /** + * Removes a media action listener. + */ + fun removeActionListener(listener: ActionListener) { + listener.onMediaActionsChanged(emptyList<RemoteAction>()) + mActionListeners.remove(listener) + } + + /** + * Adds a new media metadata listener. + */ + fun addMetadataListener(listener: MetadataListener) { + if (!mMetadataListeners.contains(listener)) { + mMetadataListeners.add(listener) + listener.onMediaMetadataChanged(mediaMetadata) + } + } + + /** + * Removes a media metadata listener. + */ + fun removeMetadataListener(listener: MetadataListener) { + listener.onMediaMetadataChanged(null) + mMetadataListeners.remove(listener) + } + + /** + * Adds a new token listener. + */ + fun addTokenListener(listener: TokenListener) { + if (!mTokenListeners.contains(listener)) { + mTokenListeners.add(listener) + listener.onMediaSessionTokenChanged(token) + } + } + + /** + * Removes a token listener. + */ + fun removeTokenListener(listener: TokenListener) { + listener.onMediaSessionTokenChanged(null) + mTokenListeners.remove(listener) + } + + private val token: MediaSession.Token? + get() = if (mMediaController == null) { + null + } else mMediaController!!.sessionToken + private val mediaMetadata: MediaMetadata? + get() = if (mMediaController != null) mMediaController!!.metadata else null + + private val mediaActions: List<RemoteAction?> + /** + * Gets the set of media actions currently available. + */ + get() { + if (mMediaController == null) { + return emptyList<RemoteAction>() + } + // Cache the PlaybackState since it's a Binder call. + // Safe because mMediaController is guaranteed non-null here. + val playbackState: PlaybackState = mMediaController!!.playbackState + ?: return emptyList<RemoteAction>() + val mediaActions = ArrayList<RemoteAction?>() + val isPlaying = playbackState.isActive + val actions = playbackState.actions + + // Prev action + mPrevAction.isEnabled = + actions and PlaybackState.ACTION_SKIP_TO_PREVIOUS != 0L + mediaActions.add(mPrevAction) + + // Play/pause action + if (!isPlaying && actions and PlaybackState.ACTION_PLAY != 0L) { + mediaActions.add(mPlayAction) + } else if (isPlaying && actions and PlaybackState.ACTION_PAUSE != 0L) { + mediaActions.add(mPauseAction) + } + + // Next action + mNextAction.isEnabled = + actions and PlaybackState.ACTION_SKIP_TO_NEXT != 0L + mediaActions.add(mNextAction) + return mediaActions + } + + /** @return Default [RemoteAction] sends broadcast back to SysUI. + */ + private fun getDefaultRemoteAction( + @StringRes titleAndDescription: Int, + @DrawableRes icon: Int, + action: String + ): RemoteAction { + val titleAndDescriptionStr = mContext.getString(titleAndDescription) + val intent = Intent(action) + intent.setPackage(mContext.packageName) + return RemoteAction( + Icon.createWithResource(mContext, icon), + titleAndDescriptionStr, titleAndDescriptionStr, + PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ) + } + + /** + * Re-registers the session listener for the current user. + */ + fun registerSessionListenerForCurrentUser() { + mMediaSessionManager!!.removeOnActiveSessionsChangedListener(mSessionsChangedListener) + mMediaSessionManager.addOnActiveSessionsChangedListener( + null, UserHandle.CURRENT, + mHandlerExecutor, mSessionsChangedListener + ) + } + + /** + * Tries to find and set the active media controller for the top PiP activity. + */ + private fun resolveActiveMediaController(controllers: List<MediaController>?) { + if (controllers != null) { + val topActivity = PipUtils.getTopPipActivity(mContext).first + if (topActivity != null) { + for (i in controllers.indices) { + val controller = controllers[i] + if (controller.packageName == topActivity.packageName) { + setActiveMediaController(controller) + return + } + } + } + } + setActiveMediaController(null) + } + + /** + * Sets the active media controller for the top PiP activity. + */ + private fun setActiveMediaController(controller: MediaController?) { + if (controller != mMediaController) { + if (mMediaController != null) { + mMediaController!!.unregisterCallback(mPlaybackChangedListener) + } + mMediaController = controller + controller?.registerCallback(mPlaybackChangedListener, mMainHandler) + notifyActionsChanged() + notifyMetadataChanged(mediaMetadata) + notifyTokenChanged(token) + + // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV) + } + } + + /** + * Notifies all listeners that the actions have changed. + */ + private fun notifyActionsChanged() { + if (mActionListeners.isNotEmpty()) { + val actions = mediaActions + mActionListeners.forEach( + Consumer { l: ActionListener -> l.onMediaActionsChanged(actions) }) + } + } + + /** + * Notifies all listeners that the metadata have changed. + */ + private fun notifyMetadataChanged(metadata: MediaMetadata?) { + if (mMetadataListeners.isNotEmpty()) { + mMetadataListeners.forEach(Consumer { l: MetadataListener -> + l.onMediaMetadataChanged( + metadata + ) + }) + } + } + + private fun notifyTokenChanged(token: MediaSession.Token?) { + if (mTokenListeners.isNotEmpty()) { + mTokenListeners.forEach(Consumer { l: TokenListener -> + l.onMediaSessionTokenChanged( + token + ) + }) + } + } + + companion object { + private const val SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF" + private const val ACTION_PLAY = "com.android.wm.shell.pip.PLAY" + private const val ACTION_PAUSE = "com.android.wm.shell.pip.PAUSE" + private const val ACTION_NEXT = "com.android.wm.shell.pip.NEXT" + private const val ACTION_PREV = "com.android.wm.shell.pip.PREV" + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java index 23153be72890..02b3a8862085 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.wm.shell.pip.phone; +package com.android.wm.shell.common.pip; import android.graphics.Point; import android.graphics.PointF; @@ -35,7 +35,7 @@ public class PipPinchResizingAlgorithm { private final PointF mTmpLastCentroid = new PointF(); /** - * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in + * Updates resizeBoundsOut with the new bounds of the PIP, and returns the angle in * degrees that the PIP should be rotated. */ public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java index dd30137813e5..007052ee012a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.wm.shell.pip; +package com.android.wm.shell.common.pip; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT; import android.graphics.Rect; @@ -39,14 +39,14 @@ public class PipSnapAlgorithm { } /** - * @return returns a fraction that describes where along the {@param movementBounds} the - * {@param stackBounds} are. If the {@param stackBounds} are not currently on the - * {@param movementBounds} exactly, then they will be snapped to the movement bounds. + * @return returns a fraction that describes where along the movementBounds the + * stackBounds are. If the stackBounds are not currently on the + * movementBounds exactly, then they will be snapped to the movement bounds. * stashType dictates whether the PiP is stashed (off-screen) or not. If * that's the case, we will have to do some math to calculate the snap fraction * correctly. * - * The fraction is defined in a clockwise fashion against the {@param movementBounds}: + * The fraction is defined in a clockwise fashion against the movementBounds: * * 0 1 * 4 +---+ 1 @@ -58,10 +58,10 @@ public class PipSnapAlgorithm { @PipBoundsState.StashType int stashType) { final Rect tmpBounds = new Rect(); snapRectToClosestEdge(stackBounds, movementBounds, tmpBounds, stashType); - final float widthFraction = (float) (tmpBounds.left - movementBounds.left) / - movementBounds.width(); - final float heightFraction = (float) (tmpBounds.top - movementBounds.top) / - movementBounds.height(); + final float widthFraction = (float) (tmpBounds.left - movementBounds.left) + / movementBounds.width(); + final float heightFraction = (float) (tmpBounds.top - movementBounds.top) + / movementBounds.height(); if (tmpBounds.top == movementBounds.top) { return widthFraction; } else if (tmpBounds.left == movementBounds.right) { @@ -74,10 +74,10 @@ public class PipSnapAlgorithm { } /** - * Moves the {@param stackBounds} along the {@param movementBounds} to the given snap fraction. + * Moves the stackBounds along the movementBounds to the given snap fraction. * See {@link #getSnapFraction(Rect, Rect)}. * - * The fraction is define in a clockwise fashion against the {@param movementBounds}: + * The fraction is define in a clockwise fashion against the movementBounds: * * 0 1 * 4 +---+ 1 @@ -122,11 +122,11 @@ public class PipSnapAlgorithm { } /** - * Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes - * the new bounds out to {@param boundsOut}. + * Snaps the stackBounds to the closest edge of the movementBounds and writes + * the new bounds out to boundsOut. */ @VisibleForTesting - void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut, + public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut, @PipBoundsState.StashType int stashType) { int leftEdge = stackBounds.left; if (stashType == STASH_TYPE_LEFT) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUiEventLogger.kt index 3e5a19b69a59..642dacc425d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUiEventLogger.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -13,68 +13,59 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.android.wm.shell.common.pip -package com.android.wm.shell.pip; - -import android.app.TaskInfo; -import android.content.pm.PackageManager; - -import com.android.internal.logging.UiEvent; -import com.android.internal.logging.UiEventLogger; +import android.app.TaskInfo +import android.content.pm.PackageManager +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger /** * Helper class that ends PiP log to UiEvent, see also go/uievent */ -public class PipUiEventLogger { - - private static final int INVALID_PACKAGE_UID = -1; - - private final UiEventLogger mUiEventLogger; - private final PackageManager mPackageManager; - - private String mPackageName; - private int mPackageUid = INVALID_PACKAGE_UID; - - public PipUiEventLogger(UiEventLogger uiEventLogger, PackageManager packageManager) { - mUiEventLogger = uiEventLogger; - mPackageManager = packageManager; - } - - public void setTaskInfo(TaskInfo taskInfo) { - if (taskInfo != null && taskInfo.topActivity != null) { - mPackageName = taskInfo.topActivity.getPackageName(); - mPackageUid = getUid(mPackageName, taskInfo.userId); +class PipUiEventLogger( + private val mUiEventLogger: UiEventLogger, + private val mPackageManager: PackageManager +) { + private var mPackageName: String? = null + private var mPackageUid = INVALID_PACKAGE_UID + fun setTaskInfo(taskInfo: TaskInfo?) { + if (taskInfo?.topActivity != null) { + // safe because topActivity is guaranteed non-null here + mPackageName = taskInfo.topActivity!!.packageName + mPackageUid = getUid(mPackageName!!, taskInfo.userId) } else { - mPackageName = null; - mPackageUid = INVALID_PACKAGE_UID; + mPackageName = null + mPackageUid = INVALID_PACKAGE_UID } } /** * Sends log via UiEvent, reference go/uievent for how to debug locally */ - public void log(PipUiEventEnum event) { + fun log(event: PipUiEventEnum?) { if (mPackageName == null || mPackageUid == INVALID_PACKAGE_UID) { - return; + return } - mUiEventLogger.log(event, mPackageUid, mPackageName); + mUiEventLogger.log(event!!, mPackageUid, mPackageName) } - private int getUid(String packageName, int userId) { - int uid = INVALID_PACKAGE_UID; + private fun getUid(packageName: String, userId: Int): Int { + var uid = INVALID_PACKAGE_UID try { uid = mPackageManager.getApplicationInfoAsUser( - packageName, 0 /* ApplicationInfoFlags */, userId).uid; - } catch (PackageManager.NameNotFoundException e) { + packageName, 0 /* ApplicationInfoFlags */, userId + ).uid + } catch (e: PackageManager.NameNotFoundException) { // do nothing. } - return uid; + return uid } /** * Enums for logging the PiP events to UiEvent */ - public enum PipUiEventEnum implements UiEventLogger.UiEventEnum { + enum class PipUiEventEnum(private val mId: Int) : UiEventLogger.UiEventEnum { @UiEvent(doc = "Activity enters picture-in-picture mode") PICTURE_IN_PICTURE_ENTER(603), @@ -99,8 +90,10 @@ public class PipUiEventLogger { @UiEvent(doc = "Hides picture-in-picture menu") PICTURE_IN_PICTURE_HIDE_MENU(608), - @UiEvent(doc = "Changes the aspect ratio of picture-in-picture window. This is inherited" - + " from previous Tron-based logging and currently not in use.") + @UiEvent( + doc = "Changes the aspect ratio of picture-in-picture window. This is inherited" + + " from previous Tron-based logging and currently not in use." + ) PICTURE_IN_PICTURE_CHANGE_ASPECT_RATIO(609), @UiEvent(doc = "User resize of the picture-in-picture window") @@ -121,15 +114,12 @@ public class PipUiEventLogger { @UiEvent(doc = "Closes PiP with app-provided close action") PICTURE_IN_PICTURE_CUSTOM_CLOSE(1058); - private final int mId; - - PipUiEventEnum(int id) { - mId = id; + override fun getId(): Int { + return mId } + } - @Override - public int getId() { - return mId; - } + companion object { + private const val INVALID_PACKAGE_UID = -1 } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt new file mode 100644 index 000000000000..84feb03e6a40 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -0,0 +1,144 @@ +/* + * 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.common.pip + +import android.app.ActivityTaskManager +import android.app.RemoteAction +import android.app.WindowConfiguration +import android.content.ComponentName +import android.content.Context +import android.os.RemoteException +import android.os.SystemProperties +import android.util.DisplayMetrics +import android.util.Log +import android.util.Pair +import android.util.TypedValue +import android.window.TaskSnapshot +import com.android.internal.protolog.common.ProtoLog +import com.android.wm.shell.protolog.ShellProtoLogGroup +import kotlin.math.abs + +/** A class that includes convenience methods. */ +object PipUtils { + private const val TAG = "PipUtils" + + // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. + private const val EPSILON = 1e-7 + private const val ENABLE_PIP2_IMPLEMENTATION = "persist.wm.debug.enable_pip2_implementation" + + /** + * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. + * The component name may be null if no such activity exists. + */ + @JvmStatic + fun getTopPipActivity(context: Context): Pair<ComponentName?, Int> { + try { + val sysUiPackageName = context.packageName + val pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo( + WindowConfiguration.WINDOWING_MODE_PINNED, + WindowConfiguration.ACTIVITY_TYPE_UNDEFINED + ) + if (pinnedTaskInfo?.childTaskIds != null && pinnedTaskInfo.childTaskIds.isNotEmpty()) { + for (i in pinnedTaskInfo.childTaskNames.indices.reversed()) { + val cn = ComponentName.unflattenFromString( + pinnedTaskInfo.childTaskNames[i] + ) + if (cn != null && cn.packageName != sysUiPackageName) { + return Pair(cn, pinnedTaskInfo.childTaskUserIds[i]) + } + } + } + } catch (e: RemoteException) { + ProtoLog.w( + ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Unable to get pinned stack.", TAG + ) + } + return Pair(null, 0) + } + + /** + * @return the pixels for a given dp value. + */ + @JvmStatic + fun dpToPx(dpValue: Float, dm: DisplayMetrics?): Int { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, dm).toInt() + } + + /** + * @return true if the aspect ratios differ + */ + @JvmStatic + fun aspectRatioChanged(aspectRatio1: Float, aspectRatio2: Float): Boolean { + return abs(aspectRatio1 - aspectRatio2) > EPSILON + } + + /** + * Checks whether title, description and intent match. + * Comparing icons would be good, but using equals causes false negatives + */ + @JvmStatic + fun remoteActionsMatch(action1: RemoteAction?, action2: RemoteAction?): Boolean { + if (action1 === action2) return true + if (action1 == null || action2 == null) return false + return action1.isEnabled == action2.isEnabled && + action1.shouldShowIcon() == action2.shouldShowIcon() && + action1.title == action2.title && + action1.contentDescription == action2.contentDescription && + action1.actionIntent == action2.actionIntent + } + + /** + * Returns true if the actions in the lists match each other according to + * [ ][PipUtils.remoteActionsMatch], including their position. + */ + @JvmStatic + fun remoteActionsChanged(list1: List<RemoteAction?>?, list2: List<RemoteAction?>?): Boolean { + if (list1 == null && list2 == null) { + return false + } + if (list1 == null || list2 == null) { + return true + } + if (list1.size != list2.size) { + return true + } + for (i in list1.indices) { + if (!remoteActionsMatch(list1[i], list2[i])) { + return true + } + } + return false + } + + /** @return [TaskSnapshot] for a given task id. + */ + @JvmStatic + fun getTaskSnapshot(taskId: Int, isLowResolution: Boolean): TaskSnapshot? { + return if (taskId <= 0) null else try { + ActivityTaskManager.getService().getTaskSnapshot( + taskId, isLowResolution, false /* takeSnapshotIfNeeded */ + ) + } catch (e: RemoteException) { + Log.e(TAG, "Failed to get task snapshot, taskId=$taskId", e) + null + } + } + + @JvmStatic + val isPip2ExperimentEnabled: Boolean + get() = SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt new file mode 100644 index 000000000000..7b3b9efb0de0 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt @@ -0,0 +1,51 @@ +/* + * 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.common.pip + +import android.util.Size +import java.io.PrintWriter + +interface SizeSpecSource { + /** Returns max size allowed for the PIP window */ + fun getMaxSize(aspectRatio: Float): Size + + /** Returns default size for the PIP window */ + fun getDefaultSize(aspectRatio: Float): Size + + /** Returns min size allowed for the PIP window */ + fun getMinSize(aspectRatio: Float): Size + + /** Returns the adjusted size based on current size and target aspect ratio */ + fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size + + /** Overrides the minimum pip size requested by the app */ + fun setOverrideMinSize(overrideMinSize: Size?) + + /** Returns the minimum pip size requested by the app */ + fun getOverrideMinSize(): Size? + + /** Returns the minimum edge size of the override minimum size, or 0 if not set. */ + fun getOverrideMinEdgeSize(): Int { + val overrideMinSize = getOverrideMinSize() ?: return 0 + return Math.min(overrideMinSize.width, overrideMinSize.height) + } + + fun onConfigurationChanged() {} + + /** Dumps the internal state of the size spec */ + fun dump(pw: PrintWriter, prefix: String) {} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java index c76937de6669..ec2680085fb5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java @@ -76,6 +76,9 @@ public class DividerHandleView extends View { private int mCurrentHeight; private AnimatorSet mAnimator; private boolean mTouching; + private boolean mHovering; + private final int mHoveringWidth; + private final int mHoveringHeight; public DividerHandleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -87,6 +90,8 @@ public class DividerHandleView extends View { mCurrentHeight = mHeight; mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth; mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight; + mHoveringWidth = mWidth > mHeight ? ((int) (mWidth * 1.5f)) : mWidth; + mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight; } /** Sets touching state for this handle view. */ @@ -94,24 +99,32 @@ public class DividerHandleView extends View { if (touching == mTouching) { return; } + setInputState(touching, animate, mTouchingWidth, mTouchingHeight); + mTouching = touching; + } + + /** Sets hovering state for this handle view. */ + public void setHovering(boolean hovering, boolean animate) { + if (hovering == mHovering) { + return; + } + setInputState(hovering, animate, mHoveringWidth, mHoveringHeight); + mHovering = hovering; + } + + private void setInputState(boolean stateOn, boolean animate, int stateWidth, int stateHeight) { if (mAnimator != null) { mAnimator.cancel(); mAnimator = null; } if (!animate) { - if (touching) { - mCurrentWidth = mTouchingWidth; - mCurrentHeight = mTouchingHeight; - } else { - mCurrentWidth = mWidth; - mCurrentHeight = mHeight; - } + mCurrentWidth = stateOn ? stateWidth : mWidth; + mCurrentHeight = stateOn ? stateHeight : mHeight; invalidate(); } else { - animateToTarget(touching ? mTouchingWidth : mWidth, - touching ? mTouchingHeight : mHeight, touching); + animateToTarget(stateOn ? stateWidth : mWidth, + stateOn ? stateHeight : mHeight, stateOn); } - mTouching = touching; } private void animateToTarget(int targetWidth, int targetHeight, boolean touching) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java new file mode 100644 index 000000000000..a5000feae239 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java @@ -0,0 +1,479 @@ +/* + * 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.common.split; + +import static android.view.WindowManager.DOCKED_INVALID; +import static android.view.WindowManager.DOCKED_LEFT; +import static android.view.WindowManager.DOCKED_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_MINIMIZE; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_NONE; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS; +import static com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Rect; +import android.hardware.display.DisplayManager; +import android.view.Display; +import android.view.DisplayInfo; + +import java.util.ArrayList; + +/** + * Calculates the snap targets and the snap position given a position and a velocity. All positions + * here are to be interpreted as the left/top edge of the divider rectangle. + * + * @hide + */ +public class DividerSnapAlgorithm { + + private static final int MIN_FLING_VELOCITY_DP_PER_SECOND = 400; + private static final int MIN_DISMISS_VELOCITY_DP_PER_SECOND = 600; + + /** + * 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio + */ + private static final int SNAP_MODE_16_9 = 0; + + /** + * 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio) + */ + private static final int SNAP_FIXED_RATIO = 1; + + /** + * 1 snap target: 1:1 + */ + private static final int SNAP_ONLY_1_1 = 2; + + /** + * 1 snap target: minimized height, (1 - minimized height) + */ + private static final int SNAP_MODE_MINIMIZED = 3; + + private final float mMinFlingVelocityPxPerSecond; + private final float mMinDismissVelocityPxPerSecond; + private final int mDisplayWidth; + private final int mDisplayHeight; + private final int mDividerSize; + private final ArrayList<SnapTarget> mTargets = new ArrayList<>(); + private final Rect mInsets = new Rect(); + private final int mSnapMode; + private final boolean mFreeSnapMode; + private final int mMinimalSizeResizableTask; + private final int mTaskHeightInMinimizedMode; + private final float mFixedRatio; + private boolean mIsHorizontalDivision; + + /** The first target which is still splitting the screen */ + private final SnapTarget mFirstSplitTarget; + + /** The last target which is still splitting the screen */ + private final SnapTarget mLastSplitTarget; + + private final SnapTarget mDismissStartTarget; + private final SnapTarget mDismissEndTarget; + private final SnapTarget mMiddleTarget; + + public static DividerSnapAlgorithm create(Context ctx, Rect insets) { + DisplayInfo displayInfo = new DisplayInfo(); + ctx.getSystemService(DisplayManager.class).getDisplay( + Display.DEFAULT_DISPLAY).getDisplayInfo(displayInfo); + int dividerWindowWidth = ctx.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_thickness); + int dividerInsets = ctx.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_insets); + return new DividerSnapAlgorithm(ctx.getResources(), + displayInfo.logicalWidth, displayInfo.logicalHeight, + dividerWindowWidth - 2 * dividerInsets, + ctx.getApplicationContext().getResources().getConfiguration().orientation + == Configuration.ORIENTATION_PORTRAIT, + insets); + } + + public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, + boolean isHorizontalDivision, Rect insets) { + this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, + DOCKED_INVALID, false /* minimized */, true /* resizable */); + } + + public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, + boolean isHorizontalDivision, Rect insets, int dockSide) { + this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, + dockSide, false /* minimized */, true /* resizable */); + } + + public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, + boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode, + boolean isHomeResizable) { + mMinFlingVelocityPxPerSecond = + MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density; + mMinDismissVelocityPxPerSecond = + MIN_DISMISS_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density; + mDividerSize = dividerSize; + mDisplayWidth = displayWidth; + mDisplayHeight = displayHeight; + mIsHorizontalDivision = isHorizontalDivision; + mInsets.set(insets); + mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED : + res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode); + mFreeSnapMode = res.getBoolean( + com.android.internal.R.bool.config_dockedStackDividerFreeSnapMode); + mFixedRatio = res.getFraction( + com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1); + mMinimalSizeResizableTask = res.getDimensionPixelSize( + com.android.internal.R.dimen.default_minimal_size_resizable_task); + mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize( + com.android.internal.R.dimen.task_height_of_minimized_mode) : 0; + calculateTargets(isHorizontalDivision, dockSide); + mFirstSplitTarget = mTargets.get(1); + mLastSplitTarget = mTargets.get(mTargets.size() - 2); + mDismissStartTarget = mTargets.get(0); + mDismissEndTarget = mTargets.get(mTargets.size() - 1); + mMiddleTarget = mTargets.get(mTargets.size() / 2); + mMiddleTarget.isMiddleTarget = true; + } + + /** + * @return whether it's feasible to enable split screen in the current configuration, i.e. when + * snapping in the middle both tasks are larger than the minimal task size. + */ + public boolean isSplitScreenFeasible() { + int statusBarSize = mInsets.top; + int navBarSize = mIsHorizontalDivision ? mInsets.bottom : mInsets.right; + int size = mIsHorizontalDivision + ? mDisplayHeight + : mDisplayWidth; + int availableSpace = size - navBarSize - statusBarSize - mDividerSize; + return availableSpace / 2 >= mMinimalSizeResizableTask; + } + + public SnapTarget calculateSnapTarget(int position, float velocity) { + return calculateSnapTarget(position, velocity, true /* hardDismiss */); + } + + /** + * @param position the top/left position of the divider + * @param velocity current dragging velocity + * @param hardDismiss if set, make it a bit harder to get reach the dismiss targets + */ + public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardDismiss) { + if (position < mFirstSplitTarget.position && velocity < -mMinDismissVelocityPxPerSecond) { + return mDismissStartTarget; + } + if (position > mLastSplitTarget.position && velocity > mMinDismissVelocityPxPerSecond) { + return mDismissEndTarget; + } + if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) { + return snap(position, hardDismiss); + } + if (velocity < 0) { + return mFirstSplitTarget; + } else { + return mLastSplitTarget; + } + } + + public SnapTarget calculateNonDismissingSnapTarget(int position) { + SnapTarget target = snap(position, false /* hardDismiss */); + if (target == mDismissStartTarget) { + return mFirstSplitTarget; + } else if (target == mDismissEndTarget) { + return mLastSplitTarget; + } else { + return target; + } + } + + public float calculateDismissingFraction(int position) { + if (position < mFirstSplitTarget.position) { + return 1f - (float) (position - getStartInset()) + / (mFirstSplitTarget.position - getStartInset()); + } else if (position > mLastSplitTarget.position) { + return (float) (position - mLastSplitTarget.position) + / (mDismissEndTarget.position - mLastSplitTarget.position - mDividerSize); + } + return 0f; + } + + public SnapTarget getClosestDismissTarget(int position) { + if (position < mFirstSplitTarget.position) { + return mDismissStartTarget; + } else if (position > mLastSplitTarget.position) { + return mDismissEndTarget; + } else if (position - mDismissStartTarget.position + < mDismissEndTarget.position - position) { + return mDismissStartTarget; + } else { + return mDismissEndTarget; + } + } + + public SnapTarget getFirstSplitTarget() { + return mFirstSplitTarget; + } + + public SnapTarget getLastSplitTarget() { + return mLastSplitTarget; + } + + public SnapTarget getDismissStartTarget() { + return mDismissStartTarget; + } + + public SnapTarget getDismissEndTarget() { + return mDismissEndTarget; + } + + private int getStartInset() { + if (mIsHorizontalDivision) { + return mInsets.top; + } else { + return mInsets.left; + } + } + + private int getEndInset() { + if (mIsHorizontalDivision) { + return mInsets.bottom; + } else { + return mInsets.right; + } + } + + private boolean shouldApplyFreeSnapMode(int position) { + if (!mFreeSnapMode) { + return false; + } + if (!isFirstSplitTargetAvailable() || !isLastSplitTargetAvailable()) { + return false; + } + return mFirstSplitTarget.position < position && position < mLastSplitTarget.position; + } + + private SnapTarget snap(int position, boolean hardDismiss) { + if (shouldApplyFreeSnapMode(position)) { + return new SnapTarget(position, position, SNAP_TO_NONE); + } + int minIndex = -1; + float minDistance = Float.MAX_VALUE; + int size = mTargets.size(); + for (int i = 0; i < size; i++) { + SnapTarget target = mTargets.get(i); + float distance = Math.abs(position - target.position); + if (hardDismiss) { + distance /= target.distanceMultiplier; + } + if (distance < minDistance) { + minIndex = i; + minDistance = distance; + } + } + return mTargets.get(minIndex); + } + + private void calculateTargets(boolean isHorizontalDivision, int dockedSide) { + mTargets.clear(); + int dividerMax = isHorizontalDivision + ? mDisplayHeight + : mDisplayWidth; + int startPos = -mDividerSize; + if (dockedSide == DOCKED_RIGHT) { + startPos += mInsets.left; + } + mTargets.add(new SnapTarget(startPos, startPos, SNAP_TO_START_AND_DISMISS, 0.35f)); + switch (mSnapMode) { + case SNAP_MODE_16_9: + addRatio16_9Targets(isHorizontalDivision, dividerMax); + break; + case SNAP_FIXED_RATIO: + addFixedDivisionTargets(isHorizontalDivision, dividerMax); + break; + case SNAP_ONLY_1_1: + addMiddleTarget(isHorizontalDivision); + break; + case SNAP_MODE_MINIMIZED: + addMinimizedTarget(isHorizontalDivision, dockedSide); + break; + } + mTargets.add(new SnapTarget(dividerMax, dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f)); + } + + private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition, + int bottomPosition, int dividerMax) { + maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_30_70); + addMiddleTarget(isHorizontalDivision); + maybeAddTarget(bottomPosition, + dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_70_30); + } + + private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) { + int start = isHorizontalDivision ? mInsets.top : mInsets.left; + int end = isHorizontalDivision + ? mDisplayHeight - mInsets.bottom + : mDisplayWidth - mInsets.right; + int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2; + int topPosition = start + size; + int bottomPosition = end - size - mDividerSize; + addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax); + } + + private void addRatio16_9Targets(boolean isHorizontalDivision, int dividerMax) { + int start = isHorizontalDivision ? mInsets.top : mInsets.left; + int end = isHorizontalDivision + ? mDisplayHeight - mInsets.bottom + : mDisplayWidth - mInsets.right; + int startOther = isHorizontalDivision ? mInsets.left : mInsets.top; + int endOther = isHorizontalDivision + ? mDisplayWidth - mInsets.right + : mDisplayHeight - mInsets.bottom; + float size = 9.0f / 16.0f * (endOther - startOther); + int sizeInt = (int) Math.floor(size); + int topPosition = start + sizeInt; + int bottomPosition = end - sizeInt - mDividerSize; + addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax); + } + + /** + * Adds a target at {@param position} but only if the area with size of {@param smallerSize} + * meets the minimal size requirement. + */ + private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapTo) { + if (smallerSize >= mMinimalSizeResizableTask) { + mTargets.add(new SnapTarget(position, position, snapTo)); + } + } + + private void addMiddleTarget(boolean isHorizontalDivision) { + int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, + mInsets, mDisplayWidth, mDisplayHeight, mDividerSize); + mTargets.add(new SnapTarget(position, position, SNAP_TO_50_50)); + } + + private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) { + // In portrait offset the position by the statusbar height, in landscape add the statusbar + // height as well to match portrait offset + int position = mTaskHeightInMinimizedMode + mInsets.top; + if (!isHorizontalDivision) { + if (dockedSide == DOCKED_LEFT) { + position += mInsets.left; + } else if (dockedSide == DOCKED_RIGHT) { + position = mDisplayWidth - position - mInsets.right - mDividerSize; + } + } + mTargets.add(new SnapTarget(position, position, SNAP_TO_MINIMIZE)); + } + + public SnapTarget getMiddleTarget() { + return mMiddleTarget; + } + + public SnapTarget getNextTarget(SnapTarget snapTarget) { + int index = mTargets.indexOf(snapTarget); + if (index != -1 && index < mTargets.size() - 1) { + return mTargets.get(index + 1); + } + return snapTarget; + } + + public SnapTarget getPreviousTarget(SnapTarget snapTarget) { + int index = mTargets.indexOf(snapTarget); + if (index != -1 && index > 0) { + return mTargets.get(index - 1); + } + return snapTarget; + } + + /** + * @return whether or not there are more than 1 split targets that do not include the two + * dismiss targets, used in deciding to display the middle target for accessibility + */ + public boolean showMiddleSplitTargetForAccessibility() { + return (mTargets.size() - 2) > 1; + } + + public boolean isFirstSplitTargetAvailable() { + return mFirstSplitTarget != mMiddleTarget; + } + + public boolean isLastSplitTargetAvailable() { + return mLastSplitTarget != mMiddleTarget; + } + + /** + * Cycles through all non-dismiss targets with a stepping of {@param increment}. It moves left + * if {@param increment} is negative and moves right otherwise. + */ + public SnapTarget cycleNonDismissTarget(SnapTarget snapTarget, int increment) { + int index = mTargets.indexOf(snapTarget); + if (index != -1) { + SnapTarget newTarget = mTargets.get((index + mTargets.size() + increment) + % mTargets.size()); + if (newTarget == mDismissStartTarget) { + return mLastSplitTarget; + } else if (newTarget == mDismissEndTarget) { + return mFirstSplitTarget; + } else { + return newTarget; + } + } + return snapTarget; + } + + /** + * Represents a snap target for the divider. + */ + public static class SnapTarget { + /** Position of this snap target. The right/bottom edge of the top/left task snaps here. */ + public final int position; + + /** + * Like {@link #position}, but used to calculate the task bounds which might be different + * from the stack bounds. + */ + public final int taskPosition; + + /** + * An int describing the placement of the divider in this snap target. + */ + public final @SnapPosition int snapTo; + + public boolean isMiddleTarget; + + /** + * Multiplier used to calculate distance to snap position. The lower this value, the harder + * it's to snap on this target + */ + private final float distanceMultiplier; + + public SnapTarget(int position, int taskPosition, @SnapPosition int snapTo) { + this(position, taskPosition, snapTo, 1f); + } + + public SnapTarget(int position, int taskPosition, @SnapPosition int snapTo, + float distanceMultiplier) { + this.position = position; + this.taskPosition = taskPosition; + this.snapTo = snapTo; + this.distanceMultiplier = distanceMultiplier; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index 69f0bad4fb45..0b0c6937553b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -17,14 +17,19 @@ package com.android.wm.shell.common.split; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; +import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Rect; import android.os.Bundle; +import android.provider.DeviceConfig; import android.util.AttributeSet; import android.util.Property; import android.view.GestureDetector; @@ -32,6 +37,7 @@ import android.view.InsetsController; import android.view.InsetsSource; import android.view.InsetsState; import android.view.MotionEvent; +import android.view.PointerIcon; import android.view.SurfaceControlViewHost; import android.view.VelocityTracker; import android.view.View; @@ -46,7 +52,7 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.policy.DividerSnapAlgorithm; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; @@ -222,7 +228,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { for (int i = insetsState.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = insetsState.sourceAt(i); if (source.getType() == WindowInsets.Type.navigationBars() - && source.insetsRoundedCornerFrame()) { + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { mTempRect.inset(source.calculateVisibleInsets(mTempRect)); } } @@ -270,6 +276,12 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { } @Override + public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { + return PointerIcon.getSystemIcon(getContext(), + isLandscape() ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW); + } + + @Override public boolean onTouch(View v, MotionEvent event) { if (mSplitLayout == null || !mInteractive) { return false; @@ -371,6 +383,43 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { mViewHost.relayout(lp); } + @Override + public boolean onHoverEvent(MotionEvent event) { + if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED, + /* defaultValue = */ false)) { + return false; + } + + if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { + setHovering(); + return true; + } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { + releaseHovering(); + return true; + } + return false; + } + + @VisibleForTesting + void setHovering() { + mHandle.setHovering(true, true); + mHandle.animate() + .setInterpolator(Interpolators.TOUCH_RESPONSE) + .setDuration(TOUCH_ANIMATION_DURATION) + .translationZ(mTouchElevation) + .start(); + } + + @VisibleForTesting + void releaseHovering() { + mHandle.setHovering(false, true); + mHandle.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setDuration(TOUCH_RELEASE_ANIMATION_DURATION) + .translationZ(0) + .start(); + } + /** * Set divider should interactive to user or not. * @@ -384,7 +433,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive", from); mInteractive = interactive; - if (!mInteractive && mMoving) { + if (!mInteractive && hideHandle && mMoving) { final int position = mSplitLayout.getDividePosition(); mSplitLayout.flingDividePosition( mLastDraggingPosition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java new file mode 100644 index 000000000000..f25dfeafb32c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java @@ -0,0 +1,140 @@ +/* + * 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.common.split; + +import static android.view.WindowManager.DOCKED_BOTTOM; +import static android.view.WindowManager.DOCKED_INVALID; +import static android.view.WindowManager.DOCKED_LEFT; +import static android.view.WindowManager.DOCKED_RIGHT; +import static android.view.WindowManager.DOCKED_TOP; + +import android.content.res.Resources; +import android.graphics.Rect; + +/** + * Utility functions for docked stack divider used by both window manager and System UI. + * + * @hide + */ +public class DockedDividerUtils { + + public static void calculateBoundsForPosition(int position, int dockSide, Rect outRect, + int displayWidth, int displayHeight, int dividerSize) { + outRect.set(0, 0, displayWidth, displayHeight); + switch (dockSide) { + case DOCKED_LEFT: + outRect.right = position; + break; + case DOCKED_TOP: + outRect.bottom = position; + break; + case DOCKED_RIGHT: + outRect.left = position + dividerSize; + break; + case DOCKED_BOTTOM: + outRect.top = position + dividerSize; + break; + } + sanitizeStackBounds(outRect, dockSide == DOCKED_LEFT || dockSide == DOCKED_TOP); + } + + /** + * Makes sure that the bounds are always valid, i. e. they are at least one pixel high and wide. + * + * @param bounds The bounds to sanitize. + * @param topLeft Pass true if the bounds are at the top/left of the screen, false if they are + * at the bottom/right. This is used to determine in which direction to extend + * the bounds. + */ + public static void sanitizeStackBounds(Rect bounds, boolean topLeft) { + + // If the bounds are either on the top or left of the screen, rather move it further to the + // left/top to make it more offscreen. If they are on the bottom or right, push them off the + // screen by moving it even more to the bottom/right. + if (topLeft) { + if (bounds.left >= bounds.right) { + bounds.left = bounds.right - 1; + } + if (bounds.top >= bounds.bottom) { + bounds.top = bounds.bottom - 1; + } + } else { + if (bounds.right <= bounds.left) { + bounds.right = bounds.left + 1; + } + if (bounds.bottom <= bounds.top) { + bounds.bottom = bounds.top + 1; + } + } + } + + public static int calculatePositionForBounds(Rect bounds, int dockSide, int dividerSize) { + switch (dockSide) { + case DOCKED_LEFT: + return bounds.right; + case DOCKED_TOP: + return bounds.bottom; + case DOCKED_RIGHT: + return bounds.left - dividerSize; + case DOCKED_BOTTOM: + return bounds.top - dividerSize; + default: + return 0; + } + } + + public static int calculateMiddlePosition(boolean isHorizontalDivision, Rect insets, + int displayWidth, int displayHeight, int dividerSize) { + int start = isHorizontalDivision ? insets.top : insets.left; + int end = isHorizontalDivision + ? displayHeight - insets.bottom + : displayWidth - insets.right; + return start + (end - start) / 2 - dividerSize / 2; + } + + public static int invertDockSide(int dockSide) { + switch (dockSide) { + case DOCKED_LEFT: + return DOCKED_RIGHT; + case DOCKED_TOP: + return DOCKED_BOTTOM; + case DOCKED_RIGHT: + return DOCKED_LEFT; + case DOCKED_BOTTOM: + return DOCKED_TOP; + default: + return DOCKED_INVALID; + } + } + + /** Returns the inset distance from the divider window edge to the dividerview. */ + public static int getDividerInsets(Resources res) { + return res.getDimensionPixelSize(com.android.internal.R.dimen.docked_stack_divider_insets); + } + + /** Returns the size of the divider */ + public static int getDividerSize(Resources res, int dividerInsets) { + final int windowWidth = res.getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_thickness); + return windowWidth - 2 * dividerInsets; + } + + /** Returns the docked-stack side */ + public static int getDockSide(int displayWidth, int displayHeight) { + return displayWidth > displayHeight ? DOCKED_LEFT : DOCKED_TOP; + } +} 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 a9ccdf6a156f..2b1037711249 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 @@ -323,7 +323,11 @@ public class SplitDecorManager extends WindowlessWindowManager { } } if (mShown) { - fadeOutDecor(()-> animFinishedCallback.accept(true)); + fadeOutDecor(()-> { + if (mRunningAnimationCount == 0 && animFinishedCallback != null) { + animFinishedCallback.accept(true); + } + }); } else { // Decor surface is hidden so release it directly. releaseDecor(t); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index f70d3aec9ec8..4af03fd5b955 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -23,12 +23,12 @@ import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; - +import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE; -import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END; -import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START; import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR; import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS; 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 static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -58,8 +58,6 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.policy.DividerSnapAlgorithm; -import com.android.internal.policy.DockedDividerUtils; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.animation.Interpolators; @@ -512,13 +510,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange * target indicates dismissing split. */ public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) { - switch (snapTarget.flag) { - case FLAG_DISMISS_START: + switch (snapTarget.snapTo) { + case SNAP_TO_START_AND_DISMISS: flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)); break; - case FLAG_DISMISS_END: + case SNAP_TO_END_AND_DISMISS: flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)); @@ -593,9 +591,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange void flingDividePosition(int from, int to, int duration, @Nullable Runnable flingFinishedCallback) { if (from == to) { - // No animation run, still callback to stop resizing. - mSplitLayoutHandler.onLayoutSizeChanged(this); - if (flingFinishedCallback != null) { flingFinishedCallback.run(); } @@ -666,10 +661,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange set.setDuration(FLING_SWITCH_DURATION); set.addListener(new AnimatorListenerAdapter() { @Override + public void onAnimationStart(Animator animation) { + InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER, + mContext, getDividerLeash(), null /*tag*/); + } + + @Override public void onAnimationEnd(Animator animation) { mDividePosition = dividerPos; updateBounds(mDividePosition); finishCallback.accept(insets); + InteractionJankMonitorUtils.endTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); + } + + @Override + public void onAnimationCancel(Animator animation) { + InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); } }); set.start(); @@ -725,10 +732,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return bounds.width() > bounds.height(); } - public boolean isDensityChanged(int densityDpi) { - return mDensity != densityDpi; - } - /** * Return if this layout is landscape. */ @@ -773,15 +776,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) { boolean boundsChanged = false; if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) { - wct.setBounds(task1.token, mBounds1); - wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1)); + setTaskBounds(wct, task1, mBounds1); mWinBounds1.set(mBounds1); mWinToken1 = task1.token; boundsChanged = true; } if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) { - wct.setBounds(task2.token, mBounds2); - wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2)); + setTaskBounds(wct, task2, mBounds2); mWinBounds2.set(mBounds2); mWinToken2 = task2.token; boundsChanged = true; @@ -789,6 +790,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return boundsChanged; } + /** Set bounds to the {@link WindowContainerTransaction} for single task. */ + public void setTaskBounds(WindowContainerTransaction wct, + ActivityManager.RunningTaskInfo task, Rect bounds) { + wct.setBounds(task.token, bounds); + wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds)); + } + private int getSmallestWidthDp(Rect bounds) { mTempRect.set(bounds); mTempRect.inset(getDisplayStableInsets(mContext)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java index ef93a336305f..ff38b7e70410 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java @@ -16,6 +16,7 @@ package com.android.wm.shell.common.split; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -56,11 +57,44 @@ public class SplitScreenConstants { public @interface SplitPosition { } + /** The divider doesn't snap to any target and is freely placeable. */ + public static final int SNAP_TO_NONE = 0; + + /** A snap target positioned near the screen edge for a minimized task */ + public static final int SNAP_TO_MINIMIZE = 1; + + /** If the divider reaches this value, the left/top task should be dismissed. */ + public static final int SNAP_TO_START_AND_DISMISS = 2; + + /** A snap target in the first half of the screen, where the split is roughly 30-70. */ + public static final int SNAP_TO_30_70 = 3; + + /** The 50-50 snap target */ + public static final int SNAP_TO_50_50 = 4; + + /** A snap target in the latter half of the screen, where the split is roughly 70-30. */ + public static final int SNAP_TO_70_30 = 5; + + /** If the divider reaches this value, the right/bottom task should be dismissed. */ + public static final int SNAP_TO_END_AND_DISMISS = 6; + + @IntDef(prefix = { "SNAP_TO_" }, value = { + SNAP_TO_NONE, + SNAP_TO_MINIMIZE, + SNAP_TO_START_AND_DISMISS, + SNAP_TO_30_70, + SNAP_TO_50_50, + SNAP_TO_70_30, + SNAP_TO_END_AND_DISMISS + }) + public @interface SnapPosition {} + public static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD}; public static final int[] CONTROLLED_WINDOWING_MODES = {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED}; public static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE = - {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW}; + {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW, + WINDOWING_MODE_FREEFORM}; /** Flag applied to a transition change to identify it as a divider bar for animation. */ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java index 0289da916937..d7ea1c0c620d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java @@ -89,4 +89,9 @@ public class SplitScreenUtils { int userId1, int userId2) { return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2); } + + /** Generates a common log message for split screen failures */ + public static String splitFailureMessage(String caller, String reason) { + return "(" + caller + ") Splitscreen aborted: " + reason; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 62b0799618ac..c111ce623c1a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -16,12 +16,17 @@ package com.android.wm.shell.compatui; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.hardware.display.DisplayManager; +import android.provider.Settings; import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -29,6 +34,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.accessibility.AccessibilityManager; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.ShellTaskOrganizer; @@ -41,7 +47,6 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -55,6 +60,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -76,6 +82,9 @@ public class CompatUIController implements OnDisplaysChangedListener, private static final String TAG = "CompatUIController"; + // The time to wait before education and button hiding + private static final int DISAPPEAR_DELAY_MS = 5000; + /** Whether the IME is shown on display id. */ private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1); @@ -104,6 +113,13 @@ public class CompatUIController implements OnDisplaysChangedListener, private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>(); /** + * The active user aspect ratio settings button layout if there is one (there can be at most + * one active). + */ + @Nullable + private UserAspectRatioSettingsWindowManager mUserAspectRatioSettingsLayout; + + /** * The active Letterbox Education layout if there is one (there can be at most one active). * * <p>An active layout is a layout that is eligible to be shown for the associated task but @@ -121,38 +137,55 @@ public class CompatUIController implements OnDisplaysChangedListener, /** Avoid creating display context frequently for non-default display. */ private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0); + @NonNull private final Context mContext; + @NonNull private final ShellController mShellController; + @NonNull private final DisplayController mDisplayController; + @NonNull private final DisplayInsetsController mDisplayInsetsController; + @NonNull private final DisplayImeController mImeController; + @NonNull private final SyncTransactionQueue mSyncQueue; + @NonNull private final ShellExecutor mMainExecutor; + @NonNull private final Lazy<Transitions> mTransitionsLazy; + @NonNull private final DockStateReader mDockStateReader; + @NonNull private final CompatUIConfiguration mCompatUIConfiguration; // Only show each hint once automatically in the process life. + @NonNull private final CompatUIHintsState mCompatUIHintsState; + @NonNull private final CompatUIShellCommandHandler mCompatUIShellCommandHandler; - private CompatUICallback mCallback; + @NonNull + private final Function<Integer, Integer> mDisappearTimeSupplier; + + @Nullable + private CompatUICallback mCompatUICallback; // Indicates if the keyguard is currently showing, in which case compat UIs shouldn't // be shown. private boolean mKeyguardShowing; - public CompatUIController(Context context, - ShellInit shellInit, - ShellController shellController, - DisplayController displayController, - DisplayInsetsController displayInsetsController, - DisplayImeController imeController, - SyncTransactionQueue syncQueue, - ShellExecutor mainExecutor, - Lazy<Transitions> transitionsLazy, - DockStateReader dockStateReader, - CompatUIConfiguration compatUIConfiguration, - CompatUIShellCommandHandler compatUIShellCommandHandler) { + public CompatUIController(@NonNull Context context, + @NonNull ShellInit shellInit, + @NonNull ShellController shellController, + @NonNull DisplayController displayController, + @NonNull DisplayInsetsController displayInsetsController, + @NonNull DisplayImeController imeController, + @NonNull SyncTransactionQueue syncQueue, + @NonNull ShellExecutor mainExecutor, + @NonNull Lazy<Transitions> transitionsLazy, + @NonNull DockStateReader dockStateReader, + @NonNull CompatUIConfiguration compatUIConfiguration, + @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler, + @NonNull AccessibilityManager accessibilityManager) { mContext = context; mShellController = shellController; mDisplayController = displayController; @@ -165,6 +198,8 @@ public class CompatUIController implements OnDisplaysChangedListener, mDockStateReader = dockStateReader; mCompatUIConfiguration = compatUIConfiguration; mCompatUIShellCommandHandler = compatUIShellCommandHandler; + mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis( + DISAPPEAR_DELAY_MS, flags); shellInit.addInitCallback(this::onInit, this); } @@ -175,9 +210,9 @@ public class CompatUIController implements OnDisplaysChangedListener, mCompatUIShellCommandHandler.onInit(); } - /** Sets the callback for UI interactions. */ - public void setCompatUICallback(CompatUICallback callback) { - mCallback = callback; + /** Sets the callback for Compat UI interactions. */ + public void setCompatUICallback(@NonNull CompatUICallback compatUiCallback) { + mCompatUICallback = compatUiCallback; } /** @@ -187,7 +222,7 @@ public class CompatUIController implements OnDisplaysChangedListener, * @param taskInfo {@link TaskInfo} task the activity is in. * @param taskListener listener to handle the Task Surface placement. */ - public void onCompatInfoChanged(TaskInfo taskInfo, + public void onCompatInfoChanged(@NonNull TaskInfo taskInfo, @Nullable ShellTaskOrganizer.TaskListener taskListener) { if (taskInfo != null && !taskInfo.topActivityInSizeCompat) { mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); @@ -203,6 +238,18 @@ public class CompatUIController implements OnDisplaysChangedListener, createOrUpdateRestartDialogLayout(taskInfo, taskListener); if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) { createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + // The user aspect ratio button should not be handled when a new TaskInfo is + // sent because of a double tap or when in multi-window mode. + if (taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { + if (mUserAspectRatioSettingsLayout != null) { + mUserAspectRatioSettingsLayout.release(); + mUserAspectRatioSettingsLayout = null; + } + return; + } + if (!taskInfo.isFromLetterboxDoubleTap) { + createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener); + } } } @@ -280,8 +327,8 @@ public class CompatUIController implements OnDisplaysChangedListener, return mDisplaysWithIme.contains(displayId); } - private void createOrUpdateCompatLayout(TaskInfo taskInfo, - ShellTaskOrganizer.TaskListener taskListener) { + private void createOrUpdateCompatLayout(@NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId); if (layout != null) { if (layout.needsToBeRecreated(taskInfo, taskListener)) { @@ -314,7 +361,7 @@ public class CompatUIController implements OnDisplaysChangedListener, CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { return new CompatUIWindowManager(context, - taskInfo, mSyncQueue, mCallback, taskListener, + taskInfo, mSyncQueue, mCompatUICallback, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState, mCompatUIConfiguration, this::onRestartButtonClicked); } @@ -328,12 +375,12 @@ public class CompatUIController implements OnDisplaysChangedListener, mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId); onCompatInfoChanged(taskInfoState.first, taskInfoState.second); } else { - mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId); + mCompatUICallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId); } } - private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo, - ShellTaskOrganizer.TaskListener taskListener) { + private void createOrUpdateLetterboxEduLayout(@NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { if (mActiveLetterboxEduLayout != null) { if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)) { mActiveLetterboxEduLayout.release(); @@ -342,6 +389,7 @@ public class CompatUIController implements OnDisplaysChangedListener, if (!mActiveLetterboxEduLayout.updateCompatInfo(taskInfo, taskListener, showOnDisplay(mActiveLetterboxEduLayout.getDisplayId()))) { // The layout is no longer eligible to be shown, clear active layout. + mActiveLetterboxEduLayout.release(); mActiveLetterboxEduLayout = null; } return; @@ -371,19 +419,13 @@ public class CompatUIController implements OnDisplaysChangedListener, ShellTaskOrganizer.TaskListener taskListener) { return new LetterboxEduWindowManager(context, taskInfo, mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), - mTransitionsLazy.get(), this::onLetterboxEduDismissed, mDockStateReader, - mCompatUIConfiguration); + mTransitionsLazy.get(), + stateInfo -> createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second), + mDockStateReader, mCompatUIConfiguration); } - private void onLetterboxEduDismissed( - Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { - mActiveLetterboxEduLayout = null; - // We need to update the UI - createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second); - } - - private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo, - ShellTaskOrganizer.TaskListener taskListener) { + private void createOrUpdateRestartDialogLayout(@NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { RestartDialogWindowManager layout = mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId); if (layout != null) { @@ -428,7 +470,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private void onRestartDialogCallback( Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId); - mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId); + mCompatUICallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId); } private void onRestartDialogDismissCallback( @@ -437,8 +479,8 @@ public class CompatUIController implements OnDisplaysChangedListener, onCompatInfoChanged(stateInfo.first, stateInfo.second); } - private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo, - ShellTaskOrganizer.TaskListener taskListener) { + private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { if (mActiveReachabilityEduLayout != null) { if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) { mActiveReachabilityEduLayout.release(); @@ -448,6 +490,7 @@ public class CompatUIController implements OnDisplaysChangedListener, if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener, showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) { // The layout is no longer eligible to be shown, remove from active layouts. + mActiveReachabilityEduLayout.release(); mActiveReachabilityEduLayout = null; } return; @@ -478,14 +521,69 @@ public class CompatUIController implements OnDisplaysChangedListener, ShellTaskOrganizer.TaskListener taskListener) { return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), - mCompatUIConfiguration, mMainExecutor); + mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed, + mDisappearTimeSupplier); + } + + private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo, + @NonNull ShellTaskOrganizer.TaskListener taskListener) { + // We need to update the UI otherwise it will not be shown until the user relaunches the app + createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener); + } + + private void createOrUpdateUserAspectRatioSettingsLayout(@NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { + if (mUserAspectRatioSettingsLayout != null) { + if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)) { + mUserAspectRatioSettingsLayout.release(); + mUserAspectRatioSettingsLayout = null; + } else { + // UI already exists, update the UI layout. + if (!mUserAspectRatioSettingsLayout.updateCompatInfo(taskInfo, taskListener, + showOnDisplay(mUserAspectRatioSettingsLayout.getDisplayId()))) { + mUserAspectRatioSettingsLayout.release(); + mUserAspectRatioSettingsLayout = null; + } + return; + } + } + + // Create a new UI layout. + final Context context = getOrCreateDisplayContext(taskInfo.displayId); + if (context == null) { + return; + } + final UserAspectRatioSettingsWindowManager newLayout = + createUserAspectRatioSettingsWindowManager(context, taskInfo, taskListener); + if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) { + // The new layout is eligible to be shown, add it the active layouts. + mUserAspectRatioSettingsLayout = newLayout; + } + } + + @VisibleForTesting + @NonNull + UserAspectRatioSettingsWindowManager createUserAspectRatioSettingsWindowManager( + @NonNull Context context, @NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { + return new UserAspectRatioSettingsWindowManager(context, taskInfo, mSyncQueue, + taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), + mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor, + mDisappearTimeSupplier); } + private void launchUserAspectRatioSettings( + @NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) { + final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + mContext.startActivity(intent); + } private void removeLayouts(int taskId) { - final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId); - if (layout != null) { - layout.release(); + final CompatUIWindowManager compatLayout = mActiveCompatLayouts.get(taskId); + if (compatLayout != null) { + compatLayout.release(); mActiveCompatLayouts.remove(taskId); } @@ -506,6 +604,12 @@ public class CompatUIController implements OnDisplaysChangedListener, mActiveReachabilityEduLayout.release(); mActiveReachabilityEduLayout = null; } + + if (mUserAspectRatioSettingsLayout != null + && mUserAspectRatioSettingsLayout.getTaskId() == taskId) { + mUserAspectRatioSettingsLayout.release(); + mUserAspectRatioSettingsLayout = null; + } } private Context getOrCreateDisplayContext(int displayId) { @@ -561,6 +665,10 @@ public class CompatUIController implements OnDisplaysChangedListener, if (mActiveReachabilityEduLayout != null && condition.test(mActiveReachabilityEduLayout)) { callback.accept(mActiveReachabilityEduLayout); } + if (mUserAspectRatioSettingsLayout != null && condition.test( + mUserAspectRatioSettingsLayout)) { + callback.accept(mUserAspectRatioSettingsLayout); + } } /** An implementation of {@link OnInsetsChangedListener} for a given display id. */ @@ -595,4 +703,14 @@ public class CompatUIController implements OnDisplaysChangedListener, insetsChanged(insetsState); } } + + /** + * A class holding the state of the compat UI hints, which is shared between all compat UI + * window managers. + */ + static class CompatUIHintsState { + boolean mHasShownSizeCompatHint; + boolean mHasShownCameraCompatHint; + boolean mHasShownUserAspectRatioSettingsButtonHint; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index 065806df3dc8..ce3c5093fdd4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -38,6 +38,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController.CompatUICallback; +import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import java.util.function.Consumer; @@ -235,15 +236,4 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN && mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED; } - - /** - * A class holding the state of the compat UI hints, which is shared between all compat UI - * window managers. - */ - static class CompatUIHintsState { - @VisibleForTesting - boolean mHasShownSizeCompatHint; - @VisibleForTesting - boolean mHasShownCameraCompatHint; - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java index 95bb1fe1c986..5612bc8ef226 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java @@ -28,6 +28,7 @@ import android.os.SystemClock; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; @@ -36,14 +37,14 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; +import java.util.function.BiConsumer; +import java.util.function.Function; + /** * Window manager for the reachability education */ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { - // The time to wait before hiding the education - private static final long DISAPPEAR_DELAY_MS = 4000L; - private static final int REACHABILITY_LEFT_OR_UP_POSITION = 0; private static final int REACHABILITY_RIGHT_OR_BOTTOM_POSITION = 2; @@ -73,6 +74,10 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { // we need to animate them. private boolean mHasLetterboxSizeChanged; + private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback; + + private final Function<Integer, Integer> mDisappearTimeSupplier; + @Nullable @VisibleForTesting ReachabilityEduLayout mLayout; @@ -80,7 +85,9 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { ReachabilityEduWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, - CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) { + CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor, + BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback, + Function<Integer, Integer> disappearTimeSupplier) { super(context, taskInfo, syncQueue, taskListener, displayLayout); mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled; mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition; @@ -89,6 +96,8 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight; mCompatUIConfiguration = compatUIConfiguration; mMainExecutor = mainExecutor; + mOnDismissCallback = onDismissCallback; + mDisappearTimeSupplier = disappearTimeSupplier; } @Override @@ -209,7 +218,12 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { } void updateHideTime() { - mNextHideTime = SystemClock.uptimeMillis() + DISAPPEAR_DELAY_MS; + mNextHideTime = SystemClock.uptimeMillis() + getDisappearTimeMs(); + } + + private long getDisappearTimeMs() { + return mDisappearTimeSupplier.apply( + AccessibilityManager.FLAG_CONTENT_ICONS | AccessibilityManager.FLAG_CONTENT_TEXT); } private void updateVisibilityOfViews() { @@ -217,13 +231,17 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { return; } final TaskInfo lastTaskInfo = getLastTaskInfo(); + final boolean hasSeenHorizontalReachabilityEdu = + mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo); + final boolean hasSeenVerticalReachabilityEdu = + mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo); final boolean eligibleForDisplayHorizontalEducation = mForceUpdate - || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo) + || !hasSeenHorizontalReachabilityEdu || (mHasUserDoubleTapped && (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION || mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION)); final boolean eligibleForDisplayVerticalEducation = mForceUpdate - || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo) + || !hasSeenVerticalReachabilityEdu || (mHasUserDoubleTapped && (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION || mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION)); @@ -238,7 +256,16 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { availableHeight, mCompatUIConfiguration, lastTaskInfo); if (!mHasLetterboxSizeChanged) { updateHideTime(); - mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS); + final long disappearTimeMs = getDisappearTimeMs(); + mMainExecutor.executeDelayed(this::hideReachability, disappearTimeMs); + // If reachability education has been seen for the first time, trigger callback to + // display aspect ratio settings button once reachability education disappears + if (hasShownHorizontalReachabilityEduFirstTime(hasSeenHorizontalReachabilityEdu) + || hasShownVerticalReachabilityEduFirstTime( + hasSeenVerticalReachabilityEdu)) { + mMainExecutor.executeDelayed(this::triggerOnDismissCallback, + disappearTimeMs); + } } mHasUserDoubleTapped = false; } else { @@ -246,6 +273,38 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { } } + /** + * Compares the value of + * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} before and after the + * layout is shown. Horizontal reachability education is considered seen for the first time if + * prior to viewing the layout, + * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} is {@code false} + * but becomes {@code true} once the current layout is shown. + */ + private boolean hasShownHorizontalReachabilityEduFirstTime( + boolean previouslyShownHorizontalReachabilityEducation) { + return !previouslyShownHorizontalReachabilityEducation + && mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(getLastTaskInfo()); + } + + /** + * Compares the value of + * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} before and after the + * layout is shown. Horizontal reachability education is considered seen for the first time if + * prior to viewing the layout, + * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} is {@code false} + * but becomes {@code true} once the current layout is shown. + */ + private boolean hasShownVerticalReachabilityEduFirstTime( + boolean previouslyShownVerticalReachabilityEducation) { + return !previouslyShownVerticalReachabilityEducation + && mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(getLastTaskInfo()); + } + + private void triggerOnDismissCallback() { + mOnDismissCallback.accept(getLastTaskInfo(), getTaskListener()); + } + private void hideReachability() { if (mLayout == null || !shouldHideEducation()) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java new file mode 100644 index 000000000000..5eeb3b650074 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java @@ -0,0 +1,136 @@ +/* + * 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.compatui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.IdRes; +import android.annotation.NonNull; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.wm.shell.R; + +/** + * Layout for the user aspect ratio button which opens the app list page in settings + * and allows users to change apps aspect ratio. + */ +public class UserAspectRatioSettingsLayout extends LinearLayout { + + private static final float ALPHA_FULL_TRANSPARENT = 0f; + + private static final float ALPHA_FULL_OPAQUE = 1f; + + private static final long VISIBILITY_ANIMATION_DURATION_MS = 50; + + private static final String ALPHA_PROPERTY_NAME = "alpha"; + + private UserAspectRatioSettingsWindowManager mWindowManager; + + public UserAspectRatioSettingsLayout(Context context) { + this(context, null); + } + + public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + void inject(@NonNull UserAspectRatioSettingsWindowManager windowManager) { + mWindowManager = windowManager; + } + + void setUserAspectRatioSettingsHintVisibility(boolean show) { + setViewVisibility(R.id.user_aspect_ratio_settings_hint, show); + } + + void setUserAspectRatioButtonVisibility(boolean show) { + setViewVisibility(R.id.user_aspect_ratio_settings_button, show); + // Hint should never be visible without button. + if (!show) { + setUserAspectRatioSettingsHintVisibility(/* show= */ false); + } + } + + private void setViewVisibility(@IdRes int resId, boolean show) { + final View view = findViewById(resId); + int visibility = show ? View.VISIBLE : View.GONE; + if (view.getVisibility() == visibility) { + return; + } + if (show) { + showItem(view); + } else { + view.setVisibility(visibility); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + // Need to relayout after changes like hiding / showing a hint since they affect size. + // Doing this directly in setUserAspectRatioButtonVisibility can result in flaky animation. + mWindowManager.relayout(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + final ImageButton userAspectRatioButton = + findViewById(R.id.user_aspect_ratio_settings_button); + userAspectRatioButton.setOnClickListener( + view -> mWindowManager.onUserAspectRatioSettingsButtonClicked()); + userAspectRatioButton.setOnLongClickListener(view -> { + mWindowManager.onUserAspectRatioSettingsButtonLongClicked(); + return true; + }); + + final LinearLayout sizeCompatHint = findViewById(R.id.user_aspect_ratio_settings_hint); + ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text)) + .setText(R.string.user_aspect_ratio_settings_button_hint); + sizeCompatHint.setOnClickListener( + view -> setUserAspectRatioSettingsHintVisibility(/* show= */ false)); + } + + private void showItem(@NonNull View view) { + view.setVisibility(View.VISIBLE); + final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME, + ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE); + fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS); + fadeIn.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(View.VISIBLE); + } + }); + fadeIn.start(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java new file mode 100644 index 000000000000..77aefc8f7e4a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -0,0 +1,219 @@ +/* + * 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.compatui; + +import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.TaskInfo; +import android.content.Context; +import android.graphics.Rect; +import android.os.SystemClock; +import android.view.LayoutInflater; +import android.view.View; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * Window manager for the user aspect ratio settings button which allows users to go to + * app settings and change apps aspect ratio. + */ +class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract { + + private static final long SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 500L; + + private long mNextButtonHideTimeMs = -1L; + + private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnButtonClicked; + + private final Function<Integer, Integer> mDisappearTimeSupplier; + + private final ShellExecutor mShellExecutor; + + @VisibleForTesting + @NonNull + final CompatUIHintsState mCompatUIHintsState; + + @Nullable + private UserAspectRatioSettingsLayout mLayout; + + // Remember the last reported states in case visibility changes due to keyguard or IME updates. + @VisibleForTesting + boolean mHasUserAspectRatioSettingsButton; + + UserAspectRatioSettingsWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo, + @NonNull SyncTransactionQueue syncQueue, + @Nullable ShellTaskOrganizer.TaskListener taskListener, + @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState, + @NonNull BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onButtonClicked, + @NonNull ShellExecutor shellExecutor, + @NonNull Function<Integer, Integer> disappearTimeSupplier) { + super(context, taskInfo, syncQueue, taskListener, displayLayout); + mShellExecutor = shellExecutor; + mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo); + mCompatUIHintsState = compatUIHintsState; + mOnButtonClicked = onButtonClicked; + mDisappearTimeSupplier = disappearTimeSupplier; + } + + @Override + protected int getZOrder() { + return TASK_CHILD_LAYER_COMPAT_UI + 1; + } + + @Override + protected @Nullable View getLayout() { + return mLayout; + } + + @Override + protected void removeLayout() { + mLayout = null; + } + + @Override + protected boolean eligibleToShowLayout() { + return mHasUserAspectRatioSettingsButton; + } + + @Override + protected View createLayout() { + mLayout = inflateLayout(); + mLayout.inject(this); + + updateVisibilityOfViews(); + + return mLayout; + } + + @VisibleForTesting + UserAspectRatioSettingsLayout inflateLayout() { + return (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate( + R.layout.user_aspect_ratio_settings_layout, null); + } + + @Override + public boolean updateCompatInfo(@NonNull TaskInfo taskInfo, + @NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) { + final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton; + mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo); + + if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) { + return false; + } + + if (prevHasUserAspectRatioSettingsButton != mHasUserAspectRatioSettingsButton) { + updateVisibilityOfViews(); + } + return true; + } + + /** Called when the user aspect ratio settings button is clicked. */ + void onUserAspectRatioSettingsButtonClicked() { + mOnButtonClicked.accept(getLastTaskInfo(), getTaskListener()); + } + + /** Called when the user aspect ratio settings button is long clicked. */ + void onUserAspectRatioSettingsButtonLongClicked() { + if (mLayout == null) { + return; + } + mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true); + final long disappearTimeMs = getDisappearTimeMs(); + mNextButtonHideTimeMs = updateHideTime(disappearTimeMs); + mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs); + } + + @Override + @VisibleForTesting + public void updateSurfacePosition() { + if (mLayout == null) { + return; + } + // Position of the button in the container coordinate. + final Rect taskBounds = getTaskBounds(); + final Rect taskStableBounds = getTaskStableBounds(); + final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL + ? taskStableBounds.left - taskBounds.left + : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth(); + final int positionY = taskStableBounds.bottom - taskBounds.top + - mLayout.getMeasuredHeight(); + updateSurfacePosition(positionX, positionY); + } + + @VisibleForTesting + void updateVisibilityOfViews() { + if (mHasUserAspectRatioSettingsButton) { + mShellExecutor.executeDelayed(this::showUserAspectRatioButton, + SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS); + final long disappearTimeMs = getDisappearTimeMs(); + mNextButtonHideTimeMs = updateHideTime(disappearTimeMs); + mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs); + } else { + mShellExecutor.removeCallbacks(this::showUserAspectRatioButton); + mShellExecutor.execute(this::hideUserAspectRatioButton); + } + } + + private void showUserAspectRatioButton() { + if (mLayout == null) { + return; + } + mLayout.setUserAspectRatioButtonVisibility(true); + // Only show by default for the first time. + if (!mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint) { + mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true); + mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true; + } + } + + private void hideUserAspectRatioButton() { + if (mLayout == null || !isHideDelayReached(mNextButtonHideTimeMs)) { + return; + } + mLayout.setUserAspectRatioButtonVisibility(false); + } + + private boolean isHideDelayReached(long nextHideTime) { + return SystemClock.uptimeMillis() >= nextHideTime; + } + + private long updateHideTime(long hideDelay) { + return SystemClock.uptimeMillis() + hideDelay; + } + + private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) { + return taskInfo.topActivityEligibleForUserAspectRatioButton + && (taskInfo.topActivityBoundsLetterboxed + || taskInfo.isUserFullscreenOverrideEnabled); + } + + private long getDisappearTimeMs() { + return mDisappearTimeSupplier.apply(AccessibilityManager.FLAG_CONTENT_CONTROLS); + } +} 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 12d51f54a09c..b52a118c7f1e 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 @@ -25,11 +25,13 @@ 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.LaunchAdjacentController; 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.dagger.pip.TvPipModule; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -47,11 +49,11 @@ import dagger.Provides; import java.util.Optional; /** - * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only - * accessible from components within the WM subcomponent (can be explicitly exposed to the - * SysUIComponent, see {@link WMComponent}). + * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible + * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see + * {@link com.android.systemui.dagger.WMComponent}). * - * This module only defines Shell dependencies for the TV SystemUI implementation. Common + * <p>This module only defines Shell dependencies for the TV SystemUI implementation. Common * dependencies should go into {@link WMShellBaseModule}. */ @Module(includes = {TvPipModule.class}) @@ -86,13 +88,14 @@ public class TvWMShellModule { TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, @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); + transactionPool, iconProvider, recentTasks, launchAdjacentController, 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 c491fed5d896..998cd5d08c72 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 @@ -24,6 +24,7 @@ import android.content.pm.PackageManager; import android.os.Handler; import android.os.SystemProperties; import android.view.IWindowManager; +import android.view.accessibility.AccessibilityManager; import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; @@ -37,6 +38,7 @@ 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.back.ShellBackAnimationRegistry; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.common.DevicePostureController; @@ -46,6 +48,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; @@ -56,11 +59,19 @@ import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; +import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.compatui.CompatUIConfiguration; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.compatui.CompatUIShellCommandHandler; import com.android.wm.shell.desktopmode.DesktopMode; -import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -74,11 +85,6 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.keyguard.KeyguardTransitions; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedController; -import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.pip.PipMediaController; -import com.android.wm.shell.pip.PipSurfaceTransactionHelper; -import com.android.wm.shell.pip.PipUiEventLogger; -import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; @@ -112,9 +118,9 @@ import dagger.Provides; /** * 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 - * SysUIComponent, see {@link WMComponent}). + * SysUIComponent, see {@link com.android.systemui.dagger.WMComponent}). * - * This module only defines *common* dependencies across various SystemUI implementations, + * <p>This module only defines *common* dependencies across various SystemUI implementations, * dependencies that are device/form factor SystemUI implementation specific should go into their * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.) */ @@ -127,6 +133,12 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides + static FloatingContentCoordinator provideFloatingContentCoordinator() { + return new FloatingContentCoordinator(); + } + + @WMSingleton + @Provides static DisplayController provideDisplayController(Context context, IWindowManager wmService, ShellInit shellInit, @@ -228,10 +240,12 @@ public abstract class WMShellBaseModule { DisplayImeController imeController, SyncTransactionQueue syncQueue, @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy, DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration, - CompatUIShellCommandHandler compatUIShellCommandHandler) { + CompatUIShellCommandHandler compatUIShellCommandHandler, + AccessibilityManager accessibilityManager) { return new CompatUIController(context, shellInit, shellController, displayController, displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy, - dockStateReader, compatUIConfiguration, compatUIShellCommandHandler); + dockStateReader, compatUIConfiguration, compatUIShellCommandHandler, + accessibilityManager); } @WMSingleton @@ -275,6 +289,13 @@ public abstract class WMShellBaseModule { return new WindowManagerShellWrapper(mainExecutor); } + @WMSingleton + @Provides + static LaunchAdjacentController provideLaunchAdjacentController( + SyncTransactionQueue syncQueue) { + return new LaunchAdjacentController(syncQueue); + } + // // Back animation // @@ -301,16 +322,80 @@ public abstract class WMShellBaseModule { ShellController shellController, @ShellMainThread ShellExecutor shellExecutor, @ShellBackgroundThread Handler backgroundHandler, - BackAnimationBackground backAnimationBackground - ) { + BackAnimationBackground backAnimationBackground, + Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) { if (BackAnimationController.IS_ENABLED) { - return Optional.of( - new BackAnimationController(shellInit, shellController, shellExecutor, - backgroundHandler, context, backAnimationBackground)); + return shellBackAnimationRegistry.map( + (animations) -> + new BackAnimationController( + shellInit, + shellController, + shellExecutor, + backgroundHandler, + context, + backAnimationBackground, + animations)); } return Optional.empty(); } + @BindsOptionalOf + abstract ShellBackAnimationRegistry optionalBackAnimationRegistry(); + + // + // PiP (optional feature) + // + + @WMSingleton + @Provides + static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger, + PackageManager packageManager) { + return new PipUiEventLogger(uiEventLogger, packageManager); + } + + @WMSingleton + @Provides + static PipMediaController providePipMediaController(Context context, + @ShellMainThread Handler mainHandler) { + return new PipMediaController(context, mainHandler); + } + + @WMSingleton + @Provides + static SizeSpecSource provideSizeSpecSource(Context context, + PipDisplayLayoutState pipDisplayLayoutState) { + return new PhoneSizeSpecSource(context, pipDisplayLayoutState); + } + + @WMSingleton + @Provides + static PipBoundsState providePipBoundsState(Context context, + SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) { + return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState); + } + + + @WMSingleton + @Provides + static PipSnapAlgorithm providePipSnapAlgorithm() { + return new PipSnapAlgorithm(); + } + + @WMSingleton + @Provides + static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) { + return new PhonePipKeepClearAlgorithm(context); + } + + @WMSingleton + @Provides + static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, + PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, + PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, + PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) { + return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, + pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource); + } // // Bubbles (optional feature) @@ -462,40 +547,6 @@ public abstract class WMShellBaseModule { } // - // Pip (optional feature) - // - - @WMSingleton - @Provides - static FloatingContentCoordinator provideFloatingContentCoordinator() { - return new FloatingContentCoordinator(); - } - - // Needs handler for registering broadcast receivers - @WMSingleton - @Provides - static PipMediaController providePipMediaController(Context context, - @ShellMainThread Handler mainHandler) { - return new PipMediaController(context, mainHandler); - } - - @WMSingleton - @Provides - static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) { - return new PipSurfaceTransactionHelper(context); - } - - @WMSingleton - @Provides - static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger, - PackageManager packageManager) { - return new PipUiEventLogger(uiEventLogger, packageManager); - } - - @BindsOptionalOf - abstract PipTouchHandler optionalPipTouchHandler(); - - // // Recent tasks // @@ -748,30 +799,10 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Optional<DesktopMode> provideDesktopMode( - Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController) { - if (DesktopModeStatus.isProto2Enabled()) { - return desktopTasksController.map(DesktopTasksController::asDesktopMode); - } - return desktopModeController.map(DesktopModeController::asDesktopMode); + return desktopTasksController.map(DesktopTasksController::asDesktopMode); } - @BindsOptionalOf - @DynamicOverride - abstract DesktopModeController optionalDesktopModeController(); - - @WMSingleton - @Provides - static Optional<DesktopModeController> provideDesktopModeController( - @DynamicOverride Optional<Lazy<DesktopModeController>> desktopModeController) { - // Use optional-of-lazy for the dependency that this provider relies on. - // Lazy ensures that this provider will not be the cause the dependency is created - // when it will not be returned due to the condition below. - if (DesktopModeStatus.isProto1Enabled()) { - return desktopModeController.map(Lazy::get); - } - return Optional.empty(); - } @BindsOptionalOf @DynamicOverride @@ -784,7 +815,7 @@ public abstract class WMShellBaseModule { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. - if (DesktopModeStatus.isProto2Enabled()) { + if (DesktopModeStatus.isEnabled()) { return desktopTasksController.map(Lazy::get); } return Optional.empty(); @@ -801,7 +832,7 @@ public abstract class WMShellBaseModule { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. - if (DesktopModeStatus.isAnyEnabled()) { + if (DesktopModeStatus.isEnabled()) { return desktopModeTaskRepository.map(Lazy::get); } return Optional.empty(); @@ -829,8 +860,6 @@ public abstract class WMShellBaseModule { ShellTaskOrganizer shellTaskOrganizer, Optional<BubbleController> bubblesOptional, Optional<SplitScreenController> splitScreenOptional, - Optional<Pip> pipOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<UnfoldTransitionHandler> unfoldTransitionHandler, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index cff317259f1e..fd23d147b1b7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -16,6 +16,7 @@ package com.android.wm.shell.dagger; +import android.annotation.Nullable; import android.content.Context; import android.content.pm.LauncherApps; import android.os.Handler; @@ -34,27 +35,32 @@ import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; +import com.android.wm.shell.bubbles.BubbleEducationController; import com.android.wm.shell.bubbles.BubbleLogger; import com.android.wm.shell.bubbles.BubblePositioner; +import com.android.wm.shell.bubbles.properties.ProdBubbleProperties; +import com.android.wm.shell.bubbles.storage.BubblePersistentRepository; 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.DisplayLayout; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.LaunchAdjacentController; 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.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.desktopmode.DesktopModeController; +import com.android.wm.shell.dagger.back.ShellBackAnimationModule; +import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; +import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; @@ -62,27 +68,7 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionHandler; import com.android.wm.shell.freeform.FreeformTaskTransitionObserver; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.onehanded.OneHandedController; -import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipAppOpsListener; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipMediaController; -import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipSnapAlgorithm; -import com.android.wm.shell.pip.PipSurfaceTransactionHelper; -import com.android.wm.shell.pip.PipTaskOrganizer; -import com.android.wm.shell.pip.PipTransition; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUiEventLogger; -import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm; -import com.android.wm.shell.pip.phone.PhonePipMenuController; -import com.android.wm.shell.pip.phone.PipController; -import com.android.wm.shell.pip.phone.PipMotionHelper; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; -import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -115,14 +101,19 @@ import java.util.List; import java.util.Optional; /** - * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only - * accessible from components within the WM subcomponent (can be explicitly exposed to the - * SysUIComponent, see {@link WMComponent}). + * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible + * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see + * {@link WMComponent}). * - * This module only defines Shell dependencies for handheld SystemUI implementation. Common + * <p>This module only defines Shell dependencies for handheld SystemUI implementation. Common * dependencies should go into {@link WMShellBaseModule}. */ -@Module(includes = WMShellBaseModule.class) +@Module( + includes = { + WMShellBaseModule.class, + PipModule.class, + ShellBackAnimationModule.class, + }) public abstract class WMShellModule { // @@ -144,11 +135,18 @@ public abstract class WMShellModule { @WMSingleton @Provides + static BubbleEducationController provideBubbleEducationProvider(Context context) { + return new BubbleEducationController(context); + } + + @WMSingleton + @Provides static BubbleData provideBubbleData(Context context, BubbleLogger logger, BubblePositioner positioner, + BubbleEducationController educationController, @ShellMainThread ShellExecutor mainExecutor) { - return new BubbleData(context, logger, positioner, mainExecutor); + return new BubbleData(context, logger, positioner, educationController, mainExecutor); } // Note: Handler needed for LauncherApps.register @@ -176,15 +174,18 @@ public abstract class WMShellModule { @ShellMainThread Handler mainHandler, @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewTransitions taskViewTransitions, + Transitions transitions, SyncTransactionQueue syncQueue, IWindowManager wmService) { return new BubbleController(context, shellInit, shellCommandHandler, shellController, data, null /* synchronizer */, floatingContentCoordinator, - new BubbleDataRepository(context, launcherApps, mainExecutor), + new BubbleDataRepository(launcherApps, mainExecutor, + new BubblePersistentRepository(context)), statusBarService, windowManager, windowManagerShellWrapper, userManager, launcherApps, logger, taskStackListener, organizer, positioner, displayController, oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor, - taskViewTransitions, syncQueue, wmService); + taskViewTransitions, transitions, syncQueue, wmService, + ProdBubbleProperties.INSTANCE); } // @@ -197,33 +198,35 @@ public abstract class WMShellModule { Context context, @ShellMainThread Handler mainHandler, @ShellMainThread Choreographer mainChoreographer, + ShellInit shellInit, ShellTaskOrganizer taskOrganizer, DisplayController displayController, + ShellController shellController, SyncTransactionQueue syncQueue, Transitions transitions, - Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController, - Optional<SplitScreenController> splitScreenController) { - if (DesktopModeStatus.isAnyEnabled()) { + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + if (DesktopModeStatus.isEnabled()) { return new DesktopModeWindowDecorViewModel( context, mainHandler, mainChoreographer, + shellInit, taskOrganizer, displayController, + shellController, syncQueue, transitions, - desktopModeController, desktopTasksController, - splitScreenController); + rootTaskDisplayAreaOrganizer); } return new CaptionWindowDecorViewModel( - context, - mainHandler, - mainChoreographer, - taskOrganizer, - displayController, - syncQueue); + context, + mainHandler, + mainChoreographer, + taskOrganizer, + displayController, + syncQueue); } // @@ -263,8 +266,13 @@ public abstract class WMShellModule { static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler( ShellInit shellInit, Transitions transitions, - WindowDecorViewModel windowDecorViewModel) { - return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel); + Context context, + WindowDecorViewModel windowDecorViewModel, + DisplayController displayController, + @ShellMainThread ShellExecutor mainExecutor, + @ShellAnimationThread ShellExecutor animExecutor) { + return new FreeformTaskTransitionHandler(shellInit, transitions, context, + windowDecorViewModel, displayController, mainExecutor, animExecutor); } @WMSingleton @@ -327,200 +335,15 @@ public abstract class WMShellModule { TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, + Optional<WindowDecorViewModel> windowDecorViewModel, + Optional<DesktopTasksController> desktopTasksController, @ShellMainThread ShellExecutor mainExecutor) { return new SplitScreenController(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController, displayImeController, displayInsetsController, dragAndDropController, transitions, - transactionPool, iconProvider, recentTasks, mainExecutor); - } - - // - // Pip - // - - @WMSingleton - @Provides - static Optional<Pip> providePip(Context context, - ShellInit shellInit, - ShellCommandHandler shellCommandHandler, - ShellController shellController, - DisplayController displayController, - PipAnimationController pipAnimationController, - PipAppOpsListener pipAppOpsListener, - PipBoundsAlgorithm pipBoundsAlgorithm, - PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, - PipBoundsState pipBoundsState, - PipSizeSpecHandler pipSizeSpecHandler, - PipDisplayLayoutState pipDisplayLayoutState, - PipMotionHelper pipMotionHelper, - PipMediaController pipMediaController, - PhonePipMenuController phonePipMenuController, - PipTaskOrganizer pipTaskOrganizer, - PipTransitionState pipTransitionState, - PipTouchHandler pipTouchHandler, - PipTransitionController pipTransitionController, - WindowManagerShellWrapper windowManagerShellWrapper, - TaskStackListenerImpl taskStackListener, - PipParamsChangedForwarder pipParamsChangedForwarder, - DisplayInsetsController displayInsetsController, - TabletopModeController pipTabletopController, - Optional<OneHandedController> oneHandedController, - @ShellMainThread ShellExecutor mainExecutor) { - return Optional.ofNullable(PipController.create( - context, shellInit, shellCommandHandler, shellController, - displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm, - pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipDisplayLayoutState, - pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, - pipTransitionState, pipTouchHandler, pipTransitionController, - windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, - displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)); - } - - @WMSingleton - @Provides - static PipBoundsState providePipBoundsState(Context context, - PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) { - return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState); - } - - @WMSingleton - @Provides - static PipSnapAlgorithm providePipSnapAlgorithm() { - return new PipSnapAlgorithm(); - } - - @WMSingleton - @Provides - static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) { - return new PhonePipKeepClearAlgorithm(context); - } - - @WMSingleton - @Provides - static PipSizeSpecHandler providePipSizeSpecHelper(Context context, - PipDisplayLayoutState pipDisplayLayoutState) { - return new PipSizeSpecHandler(context, pipDisplayLayoutState); - } - - @WMSingleton - @Provides - static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, - PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, - PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, - PipSizeSpecHandler pipSizeSpecHandler) { - return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, - pipKeepClearAlgorithm, pipSizeSpecHandler); - } - - // Handler is used by Icon.loadDrawableAsync - @WMSingleton - @Provides - static PhonePipMenuController providesPipPhoneMenuController(Context context, - PipBoundsState pipBoundsState, PipMediaController pipMediaController, - SystemWindows systemWindows, - Optional<SplitScreenController> splitScreenOptional, - PipUiEventLogger pipUiEventLogger, - @ShellMainThread ShellExecutor mainExecutor, - @ShellMainThread Handler mainHandler) { - return new PhonePipMenuController(context, pipBoundsState, pipMediaController, - systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler); - } - - @WMSingleton - @Provides - static PipTouchHandler providePipTouchHandler(Context context, - ShellInit shellInit, - PhonePipMenuController menuPhoneController, - PipBoundsAlgorithm pipBoundsAlgorithm, - PipBoundsState pipBoundsState, - PipSizeSpecHandler pipSizeSpecHandler, - PipTaskOrganizer pipTaskOrganizer, - PipMotionHelper pipMotionHelper, - FloatingContentCoordinator floatingContentCoordinator, - PipUiEventLogger pipUiEventLogger, - @ShellMainThread ShellExecutor mainExecutor) { - return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, - pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper, - floatingContentCoordinator, pipUiEventLogger, mainExecutor); - } - - @WMSingleton - @Provides - static PipTransitionState providePipTransitionState() { - return new PipTransitionState(); - } - - @WMSingleton - @Provides - static PipTaskOrganizer providePipTaskOrganizer(Context context, - SyncTransactionQueue syncTransactionQueue, - PipTransitionState pipTransitionState, - PipBoundsState pipBoundsState, - PipDisplayLayoutState pipDisplayLayoutState, - PipBoundsAlgorithm pipBoundsAlgorithm, - PhonePipMenuController menuPhoneController, - PipAnimationController pipAnimationController, - PipSurfaceTransactionHelper pipSurfaceTransactionHelper, - PipTransitionController pipTransitionController, - PipParamsChangedForwarder pipParamsChangedForwarder, - Optional<SplitScreenController> splitScreenControllerOptional, - DisplayController displayController, - PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, - @ShellMainThread ShellExecutor mainExecutor) { - return new PipTaskOrganizer(context, - syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState, - pipBoundsAlgorithm, menuPhoneController, pipAnimationController, - pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, - splitScreenControllerOptional, displayController, pipUiEventLogger, - shellTaskOrganizer, mainExecutor); - } - - @WMSingleton - @Provides - static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper - pipSurfaceTransactionHelper) { - return new PipAnimationController(pipSurfaceTransactionHelper); - } - - @WMSingleton - @Provides - static PipTransitionController providePipTransitionController(Context context, - ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, - PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm, - PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState, - PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController, - PipSurfaceTransactionHelper pipSurfaceTransactionHelper, - Optional<SplitScreenController> splitScreenOptional) { - return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, - pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController, - pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, - splitScreenOptional); - } - - @WMSingleton - @Provides - static PipAppOpsListener providePipAppOpsListener(Context context, - PipTouchHandler pipTouchHandler, - @ShellMainThread ShellExecutor mainExecutor) { - return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor); - } - - @WMSingleton - @Provides - static PipMotionHelper providePipMotionHelper(Context context, - PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, - PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm, - PipTransitionController pipTransitionController, - FloatingContentCoordinator floatingContentCoordinator) { - return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer, - menuController, pipSnapAlgorithm, pipTransitionController, - floatingContentCoordinator); - } - - @WMSingleton - @Provides - static PipParamsChangedForwarder providePipParamsChangedForwarder() { - return new PipParamsChangedForwarder(); + transactionPool, iconProvider, recentTasks, launchAdjacentController, + windowDecorViewModel, desktopTasksController, mainExecutor); } // @@ -532,13 +355,15 @@ public abstract class WMShellModule { static DefaultMixedHandler provideDefaultMixedHandler( ShellInit shellInit, Optional<SplitScreenController> splitScreenOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional, + @Nullable PipTransitionController pipTransitionController, Optional<RecentsTransitionHandler> recentsTransitionHandler, KeyguardTransitionHandler keyguardTransitionHandler, + Optional<DesktopTasksController> desktopTasksController, Optional<UnfoldTransitionHandler> unfoldHandler, Transitions transitions) { return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional, - pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler, + pipTransitionController, recentsTransitionHandler, + keyguardTransitionHandler, desktopTasksController, unfoldHandler); } @@ -573,13 +398,13 @@ public abstract class WMShellModule { animators.add(fullscreenAnimator); return new UnfoldAnimationController( - shellInit, - transactionPool, - progressProvider.get(), - animators, - unfoldTransitionHandler, - mainExecutor - ); + shellInit, + transactionPool, + progressProvider.get(), + animators, + unfoldTransitionHandler, + mainExecutor + ); } @Provides @@ -650,27 +475,10 @@ public abstract class WMShellModule { @WMSingleton @Provides @DynamicOverride - static DesktopModeController provideDesktopModeController(Context context, - ShellInit shellInit, - ShellController shellController, - ShellTaskOrganizer shellTaskOrganizer, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, - Transitions transitions, - @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, - @ShellMainThread Handler mainHandler, - @ShellMainThread ShellExecutor mainExecutor - ) { - return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer, - rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler, - mainExecutor); - } - - @WMSingleton - @Provides - @DynamicOverride static DesktopTasksController provideDesktopTasksController( Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, ShellTaskOrganizer shellTaskOrganizer, @@ -679,13 +487,16 @@ public abstract class WMShellModule { Transitions transitions, EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler, ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler, + ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, + LaunchAdjacentController launchAdjacentController, @ShellMainThread ShellExecutor mainExecutor ) { - return new DesktopTasksController(context, shellInit, shellController, displayController, - shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions, - enterDesktopTransitionHandler, exitDesktopTransitionHandler, - desktopModeTaskRepository, mainExecutor); + return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, + displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, + transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, + toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository, + launchAdjacentController, mainExecutor); } @WMSingleton @@ -697,6 +508,13 @@ public abstract class WMShellModule { @WMSingleton @Provides + static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler( + Transitions transitions) { + return new ToggleResizeDesktopTaskTransitionHandler(transitions); + } + + @WMSingleton + @Provides static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler( Transitions transitions, Context context @@ -721,8 +539,7 @@ public abstract class WMShellModule { @ShellCreateTriggerOverride @Provides static Object provideIndependentShellComponentsToCreate( - DefaultMixedHandler defaultMixedHandler, - Optional<DesktopModeController> desktopModeController) { + DefaultMixedHandler defaultMixedHandler) { return new Object(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java new file mode 100644 index 000000000000..b34c6b213df4 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java @@ -0,0 +1,62 @@ +/* + * 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.dagger.back; + +import com.android.wm.shell.back.CrossActivityAnimation; +import com.android.wm.shell.back.CrossTaskBackAnimation; +import com.android.wm.shell.back.CustomizeActivityAnimation; +import com.android.wm.shell.back.ShellBackAnimation; +import com.android.wm.shell.back.ShellBackAnimationRegistry; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; + +/** Default animation definitions for predictive back. */ +@Module +public interface ShellBackAnimationModule { + /** Default animation registry */ + @Provides + static ShellBackAnimationRegistry provideBackAnimationRegistry( + @ShellBackAnimation.CrossActivity ShellBackAnimation crossActivity, + @ShellBackAnimation.CrossTask ShellBackAnimation crossTask, + @ShellBackAnimation.CustomizeActivity ShellBackAnimation customizeActivity) { + return new ShellBackAnimationRegistry( + crossActivity, + crossTask, + customizeActivity, + /* defaultBackToHomeAnimation= */ null); + } + + /** Default cross activity back animation */ + @Binds + @ShellBackAnimation.CrossActivity + ShellBackAnimation bindCrossActivityShellBackAnimation( + CrossActivityAnimation crossActivityAnimation); + + /** Default cross task back animation */ + @Binds + @ShellBackAnimation.CrossTask + ShellBackAnimation provideCrossTaskShellBackAnimation( + CrossTaskBackAnimation crossTaskBackAnimation); + + /** Default customized activity back animation */ + @Binds + @ShellBackAnimation.CustomizeActivity + ShellBackAnimation provideCustomizeActivityShellBackAnimation( + CustomizeActivityAnimation customizeActivityAnimation); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java new file mode 100644 index 000000000000..ba882c403aed --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -0,0 +1,224 @@ +/* + * 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.dagger.pip; + +import android.content.Context; +import android.os.Handler; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.FloatingContentCoordinator; +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.TabletopModeController; +import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm; +import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.common.pip.SizeSpecSource; +import com.android.wm.shell.dagger.WMShellBaseModule; +import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipParamsChangedForwarder; +import com.android.wm.shell.pip.PipSurfaceTransactionHelper; +import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipTransition; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip.PipTransitionState; +import com.android.wm.shell.pip.phone.PhonePipMenuController; +import com.android.wm.shell.pip.phone.PipController; +import com.android.wm.shell.pip.phone.PipMotionHelper; +import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.splitscreen.SplitScreenController; +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 dagger.Module; +import dagger.Provides; + +import java.util.Optional; + +/** + * Provides dependencies from {@link com.android.wm.shell.pip}, this implementation is meant to be + * replaced by the sibling {@link Pip2Module}. + */ +@Module(includes = { + Pip1SharedModule.class, + WMShellBaseModule.class +}) +public abstract class Pip1Module { + @WMSingleton + @Provides + static Optional<Pip> providePip1(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, + ShellController shellController, + DisplayController displayController, + PipAnimationController pipAnimationController, + PipAppOpsListener pipAppOpsListener, + PipBoundsAlgorithm pipBoundsAlgorithm, + PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, + PipBoundsState pipBoundsState, + PipDisplayLayoutState pipDisplayLayoutState, + PipMotionHelper pipMotionHelper, + PipMediaController pipMediaController, + PhonePipMenuController phonePipMenuController, + PipTaskOrganizer pipTaskOrganizer, + PipTransitionState pipTransitionState, + PipTouchHandler pipTouchHandler, + PipTransitionController pipTransitionController, + WindowManagerShellWrapper windowManagerShellWrapper, + TaskStackListenerImpl taskStackListener, + PipParamsChangedForwarder pipParamsChangedForwarder, + DisplayInsetsController displayInsetsController, + TabletopModeController pipTabletopController, + Optional<OneHandedController> oneHandedController, + @ShellMainThread ShellExecutor mainExecutor) { + if (PipUtils.isPip2ExperimentEnabled()) { + return Optional.empty(); + } else { + return Optional.ofNullable(PipController.create( + context, shellInit, shellCommandHandler, shellController, + displayController, pipAnimationController, pipAppOpsListener, + pipBoundsAlgorithm, + pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState, + pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, + pipTransitionState, pipTouchHandler, pipTransitionController, + windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, + displayInsetsController, pipTabletopController, oneHandedController, + mainExecutor)); + } + } + + // Handler is used by Icon.loadDrawableAsync + @WMSingleton + @Provides + static PhonePipMenuController providesPipPhoneMenuController(Context context, + PipBoundsState pipBoundsState, PipMediaController pipMediaController, + SystemWindows systemWindows, + Optional<SplitScreenController> splitScreenOptional, + PipUiEventLogger pipUiEventLogger, + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler) { + return new PhonePipMenuController(context, pipBoundsState, pipMediaController, + systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler); + } + + @WMSingleton + @Provides + static PipTouchHandler providePipTouchHandler(Context context, + ShellInit shellInit, + PhonePipMenuController menuPhoneController, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipBoundsState pipBoundsState, + SizeSpecSource sizeSpecSource, + PipTaskOrganizer pipTaskOrganizer, + PipMotionHelper pipMotionHelper, + FloatingContentCoordinator floatingContentCoordinator, + PipUiEventLogger pipUiEventLogger, + @ShellMainThread ShellExecutor mainExecutor) { + return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, + pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper, + floatingContentCoordinator, pipUiEventLogger, mainExecutor); + } + + @WMSingleton + @Provides + static PipTransitionState providePipTransitionState() { + return new PipTransitionState(); + } + + @WMSingleton + @Provides + static PipTaskOrganizer providePipTaskOrganizer(Context context, + SyncTransactionQueue syncTransactionQueue, + PipTransitionState pipTransitionState, + PipBoundsState pipBoundsState, + PipDisplayLayoutState pipDisplayLayoutState, + PipBoundsAlgorithm pipBoundsAlgorithm, + PhonePipMenuController menuPhoneController, + PipAnimationController pipAnimationController, + PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + PipTransitionController pipTransitionController, + PipParamsChangedForwarder pipParamsChangedForwarder, + Optional<SplitScreenController> splitScreenControllerOptional, + DisplayController displayController, + PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, + @ShellMainThread ShellExecutor mainExecutor) { + return new PipTaskOrganizer(context, + syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState, + pipBoundsAlgorithm, menuPhoneController, pipAnimationController, + pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, + splitScreenControllerOptional, displayController, pipUiEventLogger, + shellTaskOrganizer, mainExecutor); + } + + @WMSingleton + @Provides + static PipTransition providePipTransition(Context context, + ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, + PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm, + PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState, + PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController, + PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + Optional<SplitScreenController> splitScreenOptional) { + return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, + pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController, + pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, + splitScreenOptional); + } + + @WMSingleton + @Provides + static PipAppOpsListener providePipAppOpsListener(Context context, + PipTouchHandler pipTouchHandler, + @ShellMainThread ShellExecutor mainExecutor) { + return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor); + } + + @WMSingleton + @Provides + static PipMotionHelper providePipMotionHelper(Context context, + PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, + PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm, + PipTransitionController pipTransitionController, + FloatingContentCoordinator floatingContentCoordinator) { + return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer, + menuController, pipSnapAlgorithm, pipTransitionController, + floatingContentCoordinator); + } + + @WMSingleton + @Provides + static PipParamsChangedForwarder providePipParamsChangedForwarder() { + return new PipParamsChangedForwarder(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java new file mode 100644 index 000000000000..b42372b869dd --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java @@ -0,0 +1,46 @@ +/* + * 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.dagger.pip; + +import android.content.Context; + +import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipSurfaceTransactionHelper; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides shared dependencies from {@link com.android.wm.shell.pip}, this implementation is + * shared with {@link TvPipModule} and possibly other form factors. + */ +@Module +public abstract class Pip1SharedModule { + @WMSingleton + @Provides + static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) { + return new PipSurfaceTransactionHelper(context); + } + + @WMSingleton + @Provides + static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper + pipSurfaceTransactionHelper) { + return new PipAnimationController(pipSurfaceTransactionHelper); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java new file mode 100644 index 000000000000..af97cf68915f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -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.dagger.pip; + +import android.annotation.NonNull; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.dagger.WMShellBaseModule; +import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.pip2.PipTransition; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be + * the successor of its sibling {@link Pip1Module}. + */ +@Module(includes = WMShellBaseModule.class) +public abstract class Pip2Module { + @WMSingleton + @Provides + static PipTransition providePipTransition(@NonNull ShellInit shellInit, + @NonNull ShellTaskOrganizer shellTaskOrganizer, + @NonNull Transitions transitions, + PipBoundsState pipBoundsState, + PipBoundsAlgorithm pipBoundsAlgorithm) { + return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, + pipBoundsAlgorithm); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java new file mode 100644 index 000000000000..570f0a3db35a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java @@ -0,0 +1,46 @@ +/* + * 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.dagger.pip; + +import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.pip.PipTransitionController; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides dependencies for external components / modules reference PiP and extracts away the + * selection of legacy and new PiP implementation. + */ +@Module(includes = { + Pip1Module.class, + Pip2Module.class +}) +public abstract class PipModule { + @WMSingleton + @Provides + static PipTransitionController providePipTransitionController( + com.android.wm.shell.pip.PipTransition legacyPipTransition, + com.android.wm.shell.pip2.PipTransition newPipTransition) { + if (PipUtils.isPip2ExperimentEnabled()) { + return newPipTransition; + } else { + return legacyPipTransition; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java index 14daae03e6b5..a9675f976fa9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.dagger; +package com.android.wm.shell.dagger.pip; import android.content.Context; import android.os.Handler; @@ -28,19 +28,21 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.common.pip.LegacySizeSpecSource; +import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.dagger.WMShellBaseModule; +import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipAppOpsListener; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUiEventLogger; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm; import com.android.wm.shell.pip.tv.TvPipBoundsController; import com.android.wm.shell.pip.tv.TvPipBoundsState; @@ -62,7 +64,9 @@ import java.util.Optional; /** * Provides TV specific dependencies for Pip. */ -@Module(includes = {WMShellBaseModule.class}) +@Module(includes = { + WMShellBaseModule.class, + Pip1SharedModule.class}) public abstract class TvPipModule { @WMSingleton @Provides @@ -126,31 +130,25 @@ public abstract class TvPipModule { @WMSingleton @Provides - static PipSnapAlgorithm providePipSnapAlgorithm() { - return new PipSnapAlgorithm(); - } - - @WMSingleton - @Provides static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context, TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, - PipSizeSpecHandler pipSizeSpecHandler) { + PipDisplayLayoutState pipDisplayLayoutState, LegacySizeSpecSource sizeSpecSource) { return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm, - pipSizeSpecHandler); + pipDisplayLayoutState, sizeSpecSource); } @WMSingleton @Provides static TvPipBoundsState provideTvPipBoundsState(Context context, - PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) { - return new TvPipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState); + LegacySizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) { + return new TvPipBoundsState(context, sizeSpecSource, pipDisplayLayoutState); } @WMSingleton @Provides - static PipSizeSpecHandler providePipSizeSpecHelper(Context context, + static LegacySizeSpecSource provideSizeSpecSource(Context context, PipDisplayLayoutState pipDisplayLayoutState) { - return new PipSizeSpecHandler(context, pipDisplayLayoutState); + return new LegacySizeSpecSource(context, pipDisplayLayoutState); } // Handler needed for loadDrawableAsync() in PipControlsViewController @@ -195,13 +193,6 @@ public abstract class TvPipModule { @WMSingleton @Provides - static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper - pipSurfaceTransactionHelper) { - return new PipAnimationController(pipSurfaceTransactionHelper); - } - - @WMSingleton - @Provides static PipTransitionState providePipTransitionState() { return new PipTransitionState(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java deleted file mode 100644 index b9d2be280efb..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ /dev/null @@ -1,512 +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.desktopmode; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_NONE; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_FRONT; - -import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; -import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE; - -import android.app.ActivityManager.RunningTaskInfo; -import android.app.WindowConfiguration; -import android.content.Context; -import android.database.ContentObserver; -import android.graphics.Region; -import android.net.Uri; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.UserHandle; -import android.provider.Settings; -import android.util.ArraySet; -import android.view.SurfaceControl; -import android.view.WindowManager; -import android.window.DisplayAreaInfo; -import android.window.TransitionInfo; -import android.window.TransitionRequestInfo; -import android.window.WindowContainerTransaction; - -import androidx.annotation.BinderThread; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.RootTaskDisplayAreaOrganizer; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.ExternalInterfaceBinder; -import com.android.wm.shell.common.RemoteCallable; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.sysui.ShellController; -import com.android.wm.shell.sysui.ShellInit; -import com.android.wm.shell.transition.Transitions; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.function.Consumer; - -/** - * Handles windowing changes when desktop mode system setting changes - */ -public class DesktopModeController implements RemoteCallable<DesktopModeController>, - Transitions.TransitionHandler { - - private final Context mContext; - private final ShellController mShellController; - private final ShellTaskOrganizer mShellTaskOrganizer; - private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; - private final Transitions mTransitions; - private final DesktopModeTaskRepository mDesktopModeTaskRepository; - private final ShellExecutor mMainExecutor; - private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl(); - private final SettingsObserver mSettingsObserver; - - public DesktopModeController(Context context, - ShellInit shellInit, - ShellController shellController, - ShellTaskOrganizer shellTaskOrganizer, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, - Transitions transitions, - DesktopModeTaskRepository desktopModeTaskRepository, - @ShellMainThread Handler mainHandler, - @ShellMainThread ShellExecutor mainExecutor) { - mContext = context; - mShellController = shellController; - mShellTaskOrganizer = shellTaskOrganizer; - mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; - mTransitions = transitions; - mDesktopModeTaskRepository = desktopModeTaskRepository; - mMainExecutor = mainExecutor; - mSettingsObserver = new SettingsObserver(mContext, mainHandler); - if (DesktopModeStatus.isProto1Enabled()) { - shellInit.addInitCallback(this::onInit, this); - } - } - - private void onInit() { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController"); - mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE, - this::createExternalInterface, this); - mSettingsObserver.observe(); - if (DesktopModeStatus.isActive(mContext)) { - updateDesktopModeActive(true); - } - mTransitions.addHandler(this); - } - - @Override - public Context getContext() { - return mContext; - } - - @Override - public ShellExecutor getRemoteCallExecutor() { - return mMainExecutor; - } - - /** - * Get connection interface between sysui and shell - */ - public DesktopMode asDesktopMode() { - return mDesktopModeImpl; - } - - /** - * Creates a new instance of the external interface to pass to another process. - */ - private ExternalInterfaceBinder createExternalInterface() { - return new IDesktopModeImpl(this); - } - - /** - * Adds a listener to find out about changes in the visibility of freeform tasks. - * - * @param listener the listener to add. - * @param callbackExecutor the executor to call the listener on. - */ - public void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener, - Executor callbackExecutor) { - mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor); - } - - /** - * Adds a listener to track changes to corners of desktop mode tasks. - * @param listener the listener to add. - * @param callbackExecutor the executor to call the listener on. - */ - public void addTaskCornerListener(Consumer<Region> listener, - Executor callbackExecutor) { - mDesktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor); - } - - @VisibleForTesting - void updateDesktopModeActive(boolean active) { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active); - - int displayId = mContext.getDisplayId(); - - ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId); - - WindowContainerTransaction wct = new WindowContainerTransaction(); - // Reset freeform windowing mode that is set per task level so tasks inherit it - clearFreeformForStandardTasks(runningTasks, wct); - if (active) { - moveHomeBehindVisibleTasks(runningTasks, wct); - setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct); - } else { - clearBoundsForStandardTasks(runningTasks, wct); - setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct); - } - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - mTransitions.startTransition(TRANSIT_CHANGE, wct, null); - } else { - mRootTaskDisplayAreaOrganizer.applyTransaction(wct); - } - } - - private WindowContainerTransaction clearBoundsForStandardTasks( - ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks"); - for (RunningTaskInfo taskInfo : runningTasks) { - if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s", - taskInfo.token, taskInfo); - wct.setBounds(taskInfo.token, null); - } - } - return wct; - } - - private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks, - WindowContainerTransaction wct) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks"); - for (RunningTaskInfo taskInfo : runningTasks) { - if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM - && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, - "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token, - taskInfo); - wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); - } - } - } - - private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks, - WindowContainerTransaction wct) { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks"); - RunningTaskInfo homeTask = null; - ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>(); - for (RunningTaskInfo taskInfo : runningTasks) { - if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) { - homeTask = taskInfo; - } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD - && taskInfo.isVisible()) { - visibleTasks.add(taskInfo); - } - } - if (homeTask == null) { - ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found"); - } else { - ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d", - visibleTasks.size()); - wct.reorder(homeTask.getToken(), true /* onTop */); - for (RunningTaskInfo task : visibleTasks) { - wct.reorder(task.getToken(), true /* onTop */); - } - } - } - - private void setDisplayAreaWindowingMode(int displayId, - @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) { - DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo( - displayId); - if (displayAreaInfo == null) { - ProtoLog.e(WM_SHELL_DESKTOP_MODE, - "unable to update windowing mode for display %d display not found", displayId); - return; - } - - ProtoLog.v(WM_SHELL_DESKTOP_MODE, - "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId, - displayAreaInfo.configuration.windowConfiguration.getWindowingMode(), - windowingMode); - - wct.setWindowingMode(displayAreaInfo.token, windowingMode); - } - - /** - * Show apps on desktop - */ - void showDesktopApps(int displayId) { - // Bring apps to front, ignoring their visibility status to always ensure they are on top. - WindowContainerTransaction wct = new WindowContainerTransaction(); - bringDesktopAppsToFront(displayId, wct); - - if (!wct.isEmpty()) { - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - // TODO(b/268662477): add animation for the transition - mTransitions.startTransition(TRANSIT_NONE, wct, null /* handler */); - } else { - mShellTaskOrganizer.applyTransaction(wct); - } - } - } - - /** Get number of tasks that are marked as visible */ - int getVisibleTaskCount(int displayId) { - return mDesktopModeTaskRepository.getVisibleTaskCount(displayId); - } - - private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) { - final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId); - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size()); - - final List<RunningTaskInfo> taskInfos = new ArrayList<>(); - for (Integer taskId : activeTasks) { - RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId); - if (taskInfo != null) { - taskInfos.add(taskInfo); - } - } - - if (taskInfos.isEmpty()) { - return; - } - - moveHomeTaskToFront(wct); - - ProtoLog.d(WM_SHELL_DESKTOP_MODE, - "bringDesktopAppsToFront: reordering all active tasks to the front"); - final List<Integer> allTasksInZOrder = - mDesktopModeTaskRepository.getFreeformTasksInZOrder(); - // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last - // in the WCT. - taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId))); - for (RunningTaskInfo task : taskInfos) { - wct.reorder(task.token, true); - } - } - - private void moveHomeTaskToFront(WindowContainerTransaction wct) { - for (RunningTaskInfo task : mShellTaskOrganizer.getRunningTasks(mContext.getDisplayId())) { - if (task.getActivityType() == ACTIVITY_TYPE_HOME) { - wct.reorder(task.token, true /* onTop */); - return; - } - } - } - - /** - * Update corner rects stored for a specific task - * @param taskId task to update - * @param taskCorners task's new corner handles - */ - public void onTaskCornersChanged(int taskId, Region taskCorners) { - mDesktopModeTaskRepository.updateTaskCorners(taskId, taskCorners); - } - - /** - * Remove corners saved for a task. Likely used due to task closure. - * @param taskId task to remove - */ - public void removeCornersForTask(int taskId) { - mDesktopModeTaskRepository.removeTaskCorners(taskId); - } - - /** - * Moves a specifc task to the front. - * @param taskInfo the task to show in front. - */ - public void moveTaskToFront(RunningTaskInfo taskInfo) { - WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reorder(taskInfo.token, true /* onTop */); - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null); - } else { - mShellTaskOrganizer.applyTransaction(wct); - } - } - - /** - * Turn desktop mode on or off - * @param active the desired state for desktop mode setting - */ - public void setDesktopModeActive(boolean active) { - int value = active ? 1 : 0; - Settings.System.putInt(mContext.getContentResolver(), Settings.System.DESKTOP_MODE, value); - } - - /** - * Returns the windowing mode of the display area with the specified displayId. - * @param displayId - * @return - */ - public int getDisplayAreaWindowingMode(int displayId) { - return mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) - .configuration.windowConfiguration.getWindowingMode(); - } - - @Override - public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - // This handler should never be the sole handler, so should not animate anything. - return false; - } - - @Nullable - @Override - public WindowContainerTransaction handleRequest(@NonNull IBinder transition, - @NonNull TransitionRequestInfo request) { - RunningTaskInfo triggerTask = request.getTriggerTask(); - // Only do anything if we are in desktop mode and opening/moving-to-front a task/app in - // freeform - if (!DesktopModeStatus.isActive(mContext)) { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, - "skip shell transition request: desktop mode not active"); - return null; - } - if (request.getType() != TRANSIT_OPEN && request.getType() != TRANSIT_TO_FRONT) { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, - "skip shell transition request: unsupported type %s", - WindowManager.transitTypeToString(request.getType())); - return null; - } - if (triggerTask == null || triggerTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task"); - return null; - } - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request); - - WindowContainerTransaction wct = new WindowContainerTransaction(); - bringDesktopAppsToFront(triggerTask.displayId, wct); - wct.reorder(triggerTask.token, true /* onTop */); - - return wct; - } - - /** - * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE} - */ - private final class SettingsObserver extends ContentObserver { - - private final Uri mDesktopModeSetting = Settings.System.getUriFor( - Settings.System.DESKTOP_MODE); - - private final Context mContext; - - SettingsObserver(Context context, Handler handler) { - super(handler); - mContext = context; - } - - public void observe() { - // TODO(b/242867463): listen for setting change for all users - mContext.getContentResolver().registerContentObserver(mDesktopModeSetting, - false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT); - } - - @Override - public void onChange(boolean selfChange, @Nullable Uri uri) { - if (mDesktopModeSetting.equals(uri)) { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting"); - desktopModeSettingChanged(); - } - } - - private void desktopModeSettingChanged() { - boolean enabled = DesktopModeStatus.isActive(mContext); - updateDesktopModeActive(enabled); - } - } - - /** - * The interface for calls from outside the shell, within the host process. - */ - @ExternalThread - private final class DesktopModeImpl implements DesktopMode { - - @Override - public void addVisibleTasksListener( - DesktopModeTaskRepository.VisibleTasksListener listener, - Executor callbackExecutor) { - mMainExecutor.execute(() -> { - DesktopModeController.this.addVisibleTasksListener(listener, callbackExecutor); - }); - } - - @Override - public void addDesktopGestureExclusionRegionListener(Consumer<Region> listener, - Executor callbackExecutor) { - mMainExecutor.execute(() -> { - DesktopModeController.this.addTaskCornerListener(listener, callbackExecutor); - }); - } - } - - /** - * The interface for calls from outside the host process. - */ - @BinderThread - private static class IDesktopModeImpl extends IDesktopMode.Stub - implements ExternalInterfaceBinder { - - private DesktopModeController mController; - - IDesktopModeImpl(DesktopModeController controller) { - mController = controller; - } - - /** - * Invalidates this instance, preventing future calls from updating the controller. - */ - @Override - public void invalidate() { - mController = null; - } - - @Override - public void showDesktopApps(int displayId) { - executeRemoteCallWithTaskPermission(mController, "showDesktopApps", - controller -> controller.showDesktopApps(displayId)); - } - - @Override - public int getVisibleTaskCount(int displayId) throws RemoteException { - int[] result = new int[1]; - executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount", - controller -> result[0] = controller.getVisibleTaskCount(displayId), - true /* blocking */ - ); - return result[0]; - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java index 76ca68bbfa75..77831136b0bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java @@ -16,14 +16,7 @@ package com.android.wm.shell.desktopmode; -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; - -import android.content.Context; import android.os.SystemProperties; -import android.os.UserHandle; -import android.provider.Settings; - -import com.android.internal.protolog.common.ProtoLog; /** * Constants for desktop mode feature @@ -31,13 +24,7 @@ import com.android.internal.protolog.common.ProtoLog; public class DesktopModeStatus { /** - * Flag to indicate whether desktop mode is available on the device - */ - private static final boolean IS_SUPPORTED = SystemProperties.getBoolean( - "persist.wm.debug.desktop_mode", false); - - /** - * Flag to indicate whether desktop mode proto 2 is available on the device + * Flag to indicate whether desktop mode proto is available on the device */ private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean( "persist.wm.debug.desktop_mode_2", false); @@ -54,29 +41,23 @@ public class DesktopModeStatus { public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean( "persist.wm.debug.desktop_change_display", false); + /** - * Return {@code true} if desktop mode support is enabled + * Flag to indicate that desktop stashing is enabled. + * When enabled, swiping home from desktop stashes the open apps. Next app that launches, + * will be added to the desktop. */ - public static boolean isProto1Enabled() { - return IS_SUPPORTED; - } + private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean( + "persist.wm.debug.desktop_stashing", false); /** * Return {@code true} is desktop windowing proto 2 is enabled */ - public static boolean isProto2Enabled() { + public static boolean isEnabled() { return IS_PROTO2_ENABLED; } /** - * Return {@code true} if proto 1 or 2 is enabled. - * Can be used to guard logic that is common for both prototypes. - */ - public static boolean isAnyEnabled() { - return isProto1Enabled() || isProto2Enabled(); - } - - /** * Return {@code true} if veiled resizing is active. If false, fluid resizing is used. */ public static boolean isVeiledResizeEnabled() { @@ -84,25 +65,10 @@ public class DesktopModeStatus { } /** - * Check if desktop mode is active - * - * @return {@code true} if active + * Return {@code true} if desktop task stashing is enabled when going home. + * Allows users to use home screen to add tasks to desktop. */ - public static boolean isActive(Context context) { - if (!isAnyEnabled()) { - return false; - } - if (isProto2Enabled()) { - // Desktop mode is always active in prototype 2 - return true; - } - try { - int result = Settings.System.getIntForUser(context.getContentResolver(), - Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT); - return result != 0; - } catch (Exception e) { - ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e); - return false; - } + public static boolean isStashingEnabled() { + return IS_STASHING_ENABLED; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 3ab175d3b68a..c0fc02fadd4d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -25,6 +25,7 @@ import androidx.core.util.keyIterator import androidx.core.util.valueIterator import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.util.KtProtoLog +import java.io.PrintWriter import java.util.concurrent.Executor import java.util.function.Consumer @@ -43,6 +44,7 @@ class DesktopModeTaskRepository { */ val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), + var stashed: Boolean = false ) // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0). @@ -50,8 +52,8 @@ class DesktopModeTaskRepository { private val activeTasksListeners = ArraySet<ActiveTasksListener>() // Track visible tasks separately because a task may be part of the desktop but not visible. private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>() - // Track corners of desktop tasks, used to determine gesture exclusion - private val desktopCorners = SparseArray<Region>() + // Track corner/caption regions of desktop tasks, used to determine gesture exclusion + private val desktopExclusionRegions = SparseArray<Region>() private var desktopGestureExclusionListener: Consumer<Region>? = null private var desktopGestureExclusionExecutor: Executor? = null @@ -85,17 +87,20 @@ class DesktopModeTaskRepository { visibleTasksListeners[visibleTasksListener] = executor displayData.keyIterator().forEach { displayId -> val visibleTasks = getVisibleTaskCount(displayId) + val stashed = isStashed(displayId) executor.execute { visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0) + visibleTasksListener.onStashedChanged(displayId, stashed) } } } /** - * Add a Consumer which will inform other classes of changes to corners for all Desktop tasks. + * Add a Consumer which will inform other classes of changes to exclusion regions for all + * Desktop tasks. */ - fun setTaskCornerListener(cornersListener: Consumer<Region>, executor: Executor) { - desktopGestureExclusionListener = cornersListener + fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) { + desktopGestureExclusionListener = regionListener desktopGestureExclusionExecutor = executor executor.execute { desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion()) @@ -103,14 +108,14 @@ class DesktopModeTaskRepository { } /** - * Create a new merged region representative of all corners in all desktop tasks. + * Create a new merged region representative of all exclusion regions in all desktop tasks. */ private fun calculateDesktopExclusionRegion(): Region { - val desktopCornersRegion = Region() - desktopCorners.valueIterator().forEach { taskCorners -> - desktopCornersRegion.op(taskCorners, Region.Op.UNION) + val desktopExclusionRegion = Region() + desktopExclusionRegions.valueIterator().forEach { taskExclusionRegion -> + desktopExclusionRegion.op(taskExclusionRegion, Region.Op.UNION) } - return desktopCornersRegion + return desktopExclusionRegion } /** @@ -290,35 +295,82 @@ class DesktopModeTaskRepository { } /** - * Updates the active desktop corners; if desktopCorners has been accepted by - * desktopCornersListener, it will be updated in the appropriate classes. + * Updates the active desktop gesture exclusion regions; if desktopExclusionRegions has been + * accepted by desktopGestureExclusionListener, it will be updated in the + * appropriate classes. */ - fun updateTaskCorners(taskId: Int, taskCorners: Region) { - desktopCorners.put(taskId, taskCorners) + fun updateTaskExclusionRegions(taskId: Int, taskExclusionRegions: Region) { + desktopExclusionRegions.put(taskId, taskExclusionRegions) desktopGestureExclusionExecutor?.execute { desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion()) } } /** - * Removes the active desktop corners for the specified task; if desktopCorners has been - * accepted by desktopCornersListener, it will be updated in the appropriate classes. + * Removes the desktop gesture exclusion region for the specified task; if exclusionRegion + * has been accepted by desktopGestureExclusionListener, it will be updated in the + * appropriate classes. */ - fun removeTaskCorners(taskId: Int) { - desktopCorners.delete(taskId) + fun removeExclusionRegion(taskId: Int) { + desktopExclusionRegions.delete(taskId) desktopGestureExclusionExecutor?.execute { desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion()) } } /** + * Update stashed status on display with id [displayId] + */ + fun setStashed(displayId: Int, stashed: Boolean) { + val data = displayData.getOrCreate(displayId) + val oldValue = data.stashed + data.stashed = stashed + if (oldValue != stashed) { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTaskRepo: mark stashed=%b displayId=%d", + stashed, + displayId + ) + visibleTasksListeners.forEach { (listener, executor) -> + executor.execute { listener.onStashedChanged(displayId, stashed) } + } + } + } + + /** + * Check if display with id [displayId] has desktop tasks stashed + */ + fun isStashed(displayId: Int): Boolean { + return displayData[displayId]?.stashed ?: false + } + + internal fun dump(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + pw.println("${prefix}DesktopModeTaskRepository") + dumpDisplayData(pw, innerPrefix) + pw.println("${innerPrefix}freeformTasksInZOrder=${freeformTasksInZOrder.toDumpString()}") + pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}") + pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}") + } + + private fun dumpDisplayData(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + displayData.forEach { displayId, data -> + pw.println("${prefix}Display $displayId:") + pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}") + pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}") + pw.println("${innerPrefix}stashed=${data.stashed}") + } + } + + /** * Defines interface for classes that can listen to changes for active tasks in desktop mode. */ interface ActiveTasksListener { /** * Called when the active tasks change in desktop mode. */ - @JvmDefault fun onActiveTasksChanged(displayId: Int) {} } @@ -329,7 +381,15 @@ class DesktopModeTaskRepository { /** * Called when the desktop starts or stops showing freeform tasks. */ - @JvmDefault fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {} + + /** + * Called when the desktop stashed status changes. + */ + fun onStashedChanged(displayId: Int, stashed: Boolean) {} } } + +private fun <T> Iterable<T>.toDumpString(): String { + return joinToString(separator = ", ", prefix = "[", postfix = "]") +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 0f0d572f8eae..a587bed3fef0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -27,6 +27,7 @@ import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; import android.graphics.PixelFormat; +import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.SurfaceControl; @@ -47,6 +48,15 @@ import com.android.wm.shell.common.SyncTransactionQueue; * Animated visual indicator for Desktop Mode windowing transitions. */ public class DesktopModeVisualIndicator { + public static final int INVALID_INDICATOR = -1; + /** Indicates impending transition into desktop mode */ + public static final int TO_DESKTOP_INDICATOR = 1; + /** Indicates impending transition into fullscreen */ + public static final int TO_FULLSCREEN_INDICATOR = 2; + /** Indicates impending transition into split select on the left side */ + public static final int TO_SPLIT_LEFT_INDICATOR = 3; + /** Indicates impending transition into split select on the right side */ + public static final int TO_SPLIT_RIGHT_INDICATOR = 4; private final Context mContext; private final DisplayController mDisplayController; @@ -54,6 +64,7 @@ public class DesktopModeVisualIndicator { private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer; private final ActivityManager.RunningTaskInfo mTaskInfo; private final SurfaceControl mTaskSurface; + private final Rect mIndicatorRange = new Rect(); private SurfaceControl mLeash; private final SyncTransactionQueue mSyncQueue; @@ -61,11 +72,12 @@ public class DesktopModeVisualIndicator { private View mView; private boolean mIsFullscreen; + private int mType; public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue, ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController, Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer, - RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) { + RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer, int type) { mSyncQueue = syncQueue; mTaskInfo = taskInfo; mDisplayController = displayController; @@ -73,10 +85,64 @@ public class DesktopModeVisualIndicator { mTaskSurface = taskSurface; mTaskOrganizer = taskOrganizer; mRootTdaOrganizer = taskDisplayAreaOrganizer; + mType = type; + defineIndicatorRange(); createView(); } /** + * If an indicator is warranted based on the input and task bounds, return the type of + * indicator that should be created. + */ + public static int determineIndicatorType(PointF inputCoordinates, Rect taskBounds, + DisplayLayout layout, Context context) { + int transitionAreaHeight = context.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_height); + int transitionAreaWidth = context.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_width); + if (taskBounds.top <= transitionAreaHeight) return TO_FULLSCREEN_INDICATOR; + if (inputCoordinates.x <= transitionAreaWidth) return TO_SPLIT_LEFT_INDICATOR; + if (inputCoordinates.x >= layout.width() - transitionAreaWidth) { + return TO_SPLIT_RIGHT_INDICATOR; + } + return INVALID_INDICATOR; + } + + /** + * Determine range of inputs that will keep this indicator displaying. + */ + private void defineIndicatorRange() { + DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId); + int captionHeight = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.freeform_decor_caption_height); + int transitionAreaHeight = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_height); + int transitionAreaWidth = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_width); + switch (mType) { + case TO_DESKTOP_INDICATOR: + // TO_DESKTOP indicator is only dismissed on release; entire display is valid. + mIndicatorRange.set(0, 0, layout.width(), layout.height()); + break; + case TO_FULLSCREEN_INDICATOR: + // If drag results in caption going above the top edge of the display, we still + // want to transition to fullscreen. + mIndicatorRange.set(0, -captionHeight, layout.width(), transitionAreaHeight); + break; + case TO_SPLIT_LEFT_INDICATOR: + mIndicatorRange.set(0, transitionAreaHeight, transitionAreaWidth, layout.height()); + break; + case TO_SPLIT_RIGHT_INDICATOR: + mIndicatorRange.set(layout.width() - transitionAreaWidth, transitionAreaHeight, + layout.width(), layout.height()); + break; + default: + break; + } + } + + + /** * Create a fullscreen indicator with no animation */ private void createView() { @@ -85,11 +151,30 @@ public class DesktopModeVisualIndicator { final DisplayMetrics metrics = resources.getDisplayMetrics(); final int screenWidth = metrics.widthPixels; final int screenHeight = metrics.heightPixels; + mView = new View(mContext); final SurfaceControl.Builder builder = new SurfaceControl.Builder(); mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder); + String description; + switch (mType) { + case TO_DESKTOP_INDICATOR: + description = "Desktop indicator"; + break; + case TO_FULLSCREEN_INDICATOR: + description = "Fullscreen indicator"; + break; + case TO_SPLIT_LEFT_INDICATOR: + description = "Split Left indicator"; + break; + case TO_SPLIT_RIGHT_INDICATOR: + description = "Split Right indicator"; + break; + default: + description = "Invalid indicator"; + break; + } mLeash = builder - .setName("Fullscreen Indicator") + .setName(description) .setContainerLayer() .build(); t.show(mLeash); @@ -97,14 +182,14 @@ public class DesktopModeVisualIndicator { new WindowManager.LayoutParams(screenWidth, screenHeight, WindowManager.LayoutParams.TYPE_APPLICATION, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); - lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId); + lp.setTitle(description + " for Task=" + mTaskInfo.taskId); lp.setTrustedOverlay(); final WindowlessWindowManager windowManager = new WindowlessWindowManager( mTaskInfo.configuration, mLeash, null /* hostInputToken */); mViewHost = new SurfaceControlViewHost(mContext, mDisplayController.getDisplay(mTaskInfo.displayId), windowManager, - "FullscreenVisualIndicator"); + "DesktopModeVisualIndicator"); mViewHost.setView(mView, lp); // We want this indicator to be behind the dragged task, but in front of all others. t.setRelativeLayer(mLeash, mTaskSurface, -1); @@ -116,24 +201,13 @@ public class DesktopModeVisualIndicator { } /** - * Create fullscreen indicator and fades it in. - */ - public void createFullscreenIndicator() { - mIsFullscreen = true; - mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background); - final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator( - mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); - animator.start(); - } - - /** - * Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards. + * Create an indicator. Animator fades it in while expanding the bounds outwards. */ - public void createFullscreenIndicatorWithAnimatedBounds() { - mIsFullscreen = true; + public void createIndicatorWithAnimatedBounds() { + mIsFullscreen = mType == TO_FULLSCREEN_INDICATOR; mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background); final VisualIndicatorAnimator animator = VisualIndicatorAnimator - .toFullscreenAnimatorWithAnimatedBounds(mView, + .animateBounds(mView, mType, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); animator.start(); } @@ -143,6 +217,7 @@ public class DesktopModeVisualIndicator { */ public void transitionFullscreenIndicatorToFreeform() { mIsFullscreen = false; + mType = TO_DESKTOP_INDICATOR; final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator( mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); animator.start(); @@ -153,6 +228,7 @@ public class DesktopModeVisualIndicator { */ public void transitionFreeformIndicatorToFullscreen() { mIsFullscreen = true; + mType = TO_FULLSCREEN_INDICATOR; final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds( mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); @@ -160,6 +236,14 @@ public class DesktopModeVisualIndicator { } /** + * Determine if a MotionEvent is in the same range that enabled the indicator. + * Used to dismiss the indicator when a transition will no longer result from releasing. + */ + public boolean eventOutsideRange(float x, float y) { + return !mIndicatorRange.contains((int) x, (int) y); + } + + /** * Release the indicator and its components when it is no longer needed. */ public void releaseVisualIndicator(SurfaceControl.Transaction t) { @@ -210,32 +294,45 @@ public class DesktopModeVisualIndicator { * @param view the view for this indicator * @param displayLayout information about the display the transitioning task is currently on */ - public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view, - @NonNull DisplayLayout displayLayout) { - final Rect bounds = getMaxBounds(displayLayout); + public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds( + @NonNull View view, @NonNull DisplayLayout displayLayout) { + final int padding = displayLayout.stableInsets().top; + Rect startBounds = new Rect(padding, padding, + displayLayout.width() - padding, displayLayout.height() - padding); + view.getBackground().setBounds(startBounds); + final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( - view, bounds, bounds); + view, startBounds, getMaxBounds(startBounds)); animator.setInterpolator(new DecelerateInterpolator()); setupIndicatorAnimation(animator); return animator; } - - /** - * Create animator for visual indicator of fullscreen transition - * - * @param view the view for this indicator - * @param displayLayout information about the display the transitioning task is currently on - */ - public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds( - @NonNull View view, @NonNull DisplayLayout displayLayout) { + public static VisualIndicatorAnimator animateBounds( + @NonNull View view, int type, @NonNull DisplayLayout displayLayout) { final int padding = displayLayout.stableInsets().top; - Rect startBounds = new Rect(padding, padding, - displayLayout.width() - padding, displayLayout.height() - padding); + Rect startBounds = new Rect(); + switch (type) { + case TO_FULLSCREEN_INDICATOR: + startBounds.set(padding, padding, + displayLayout.width() - padding, + displayLayout.height() - padding); + break; + case TO_SPLIT_LEFT_INDICATOR: + startBounds.set(padding, padding, + displayLayout.width() / 2 - padding, + displayLayout.height() - padding); + break; + case TO_SPLIT_RIGHT_INDICATOR: + startBounds.set(displayLayout.width() / 2 + padding, padding, + displayLayout.width() - padding, + displayLayout.height() - padding); + break; + } view.getBackground().setBounds(startBounds); final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( - view, startBounds, getMaxBounds(displayLayout)); + view, startBounds, getMaxBounds(startBounds)); animator.setInterpolator(new DecelerateInterpolator()); setupIndicatorAnimation(animator); return animator; @@ -252,12 +349,13 @@ public class DesktopModeVisualIndicator { final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE; final int width = displayLayout.width(); final int height = displayLayout.height(); + Rect startBounds = new Rect(0, 0, width, height); Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2), (int) (adjustmentPercentage * height / 2), (int) (displayLayout.width() - (adjustmentPercentage * width / 2)), (int) (displayLayout.height() - (adjustmentPercentage * height / 2))); final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( - view, getMaxBounds(displayLayout), endBounds); + view, startBounds, endBounds); animator.setInterpolator(new DecelerateInterpolator()); setupIndicatorAnimation(animator); return animator; @@ -310,21 +408,17 @@ public class DesktopModeVisualIndicator { } /** - * Return the max bounds of a fullscreen indicator + * Return the max bounds of a visual indicator */ - private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) { - final int padding = displayLayout.stableInsets().top; - final int width = displayLayout.width() - 2 * padding; - final int height = displayLayout.height() - 2 * padding; - Rect endBounds = new Rect((int) (padding - - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)), - (int) (padding - - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)), - (int) (displayLayout.width() - padding - + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)), - (int) (displayLayout.height() - padding - + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height))); - return endBounds; + private static Rect getMaxBounds(Rect startBounds) { + return new Rect((int) (startBounds.left + - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())), + (int) (startBounds.top + - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height())), + (int) (startBounds.right + + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())), + (int) (startBounds.bottom + + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height()))); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 91bb155d9d01..f8dd208f96db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -21,14 +21,17 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context import android.graphics.Point +import android.graphics.PointF import android.graphics.Rect import android.graphics.Region import android.os.IBinder import android.os.SystemProperties +import android.util.DisplayMetrics.DENSITY_DEFAULT import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_NONE @@ -36,26 +39,37 @@ import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.TransitionInfo import android.window.TransitionRequestInfo -import android.window.WindowContainerToken import android.window.WindowContainerTransaction import androidx.annotation.BinderThread +import com.android.internal.policy.ScreenDecorationsUtils 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.ExecutorUtils import com.android.wm.shell.common.ExternalInterfaceBinder +import com.android.wm.shell.common.LaunchAdjacentController import com.android.wm.shell.common.RemoteCallable import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread +import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT +import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener +import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.splitscreen.SplitScreenController +import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP +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.sysui.ShellSharedConstants import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.KtProtoLog +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator +import java.io.PrintWriter import java.util.concurrent.Executor import java.util.function.Consumer @@ -63,6 +77,7 @@ import java.util.function.Consumer class DesktopTasksController( private val context: Context, shellInit: ShellInit, + private val shellCommandHandler: ShellCommandHandler, private val shellController: ShellController, private val displayController: DisplayController, private val shellTaskOrganizer: ShellTaskOrganizer, @@ -71,7 +86,10 @@ class DesktopTasksController( private val transitions: Transitions, private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler, private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler, + private val toggleResizeDesktopTaskTransitionHandler: + ToggleResizeDesktopTaskTransitionHandler, private val desktopModeTaskRepository: DesktopModeTaskRepository, + private val launchAdjacentController: LaunchAdjacentController, @ShellMainThread private val mainExecutor: ShellExecutor ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler { @@ -82,22 +100,42 @@ class DesktopTasksController( visualIndicator?.releaseVisualIndicator(t) visualIndicator = null } + private val taskVisibilityListener = object : VisibleTasksListener { + override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) { + launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks + } + } + + private val transitionAreaHeight + get() = context.resources.getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_height + ) + + private val transitionAreaWidth + get() = context.resources.getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_width + ) + + // This is public to avoid cyclic dependency; it is set by SplitScreenController + lateinit var splitScreenController: SplitScreenController init { desktopMode = DesktopModeImpl() - if (DesktopModeStatus.isProto2Enabled()) { + if (DesktopModeStatus.isEnabled()) { shellInit.addInitCallback({ onInit() }, this) } } private fun onInit() { KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") + shellCommandHandler.addDumpCallback(this::dump, this) shellController.addExternalInterface( ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE, { createExternalInterface() }, this ) transitions.addHandler(this) + desktopModeTaskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor) } /** Show all tasks, that are part of the desktop, on top of launcher */ @@ -118,78 +156,141 @@ class DesktopTasksController( } } + /** + * Stash desktop tasks on display with id [displayId]. + * + * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps + * launched in this state will be added to the desktop. Existing desktop tasks will be brought + * back to front during the launch. + */ + fun stashDesktopApps(displayId: Int) { + if (DesktopModeStatus.isStashingEnabled()) { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps") + desktopModeTaskRepository.setStashed(displayId, true) + } + } + + /** + * Clear the stashed state for the given display + */ + fun hideStashedDesktopApps(displayId: Int) { + if (DesktopModeStatus.isStashingEnabled()) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: hideStashedApps displayId=%d", + displayId + ) + desktopModeTaskRepository.setStashed(displayId, false) + } + } + /** Get number of tasks that are marked as visible */ fun getVisibleTaskCount(displayId: Int): Int { return desktopModeTaskRepository.getVisibleTaskCount(displayId) } /** Move a task with given `taskId` to desktop */ - fun moveToDesktop(taskId: Int) { - shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) } + fun moveToDesktop( + decor: DesktopModeWindowDecoration, + taskId: Int, + wct: WindowContainerTransaction = WindowContainerTransaction() + ) { + shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { + task -> moveToDesktop(decor, task, wct) + } } - /** Move a task to desktop */ - fun moveToDesktop(task: RunningTaskInfo) { + /** + * Move a task to desktop + */ + fun moveToDesktop( + decor: DesktopModeWindowDecoration, + task: RunningTaskInfo, + wct: WindowContainerTransaction = WindowContainerTransaction() + ) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToDesktop taskId=%d", task.taskId ) - val wct = WindowContainerTransaction() + exitSplitIfApplicable(wct, task) // Bring other apps to front first bringDesktopAppsToFront(task.displayId, wct) - addMoveToDesktopChanges(wct, task.token) + addMoveToDesktopChanges(wct, task) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) + enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor) } else { shellTaskOrganizer.applyTransaction(wct) } } /** - * Moves a single task to freeform and sets the taskBounds to the passed in bounds, - * startBounds + * The first part of the animated move to desktop transition. Applies the changes to move task + * to desktop mode and sets the taskBounds to the passed in bounds, startBounds. This is + * followed with a call to {@link finishMoveToDesktop} or {@link cancelMoveToDesktop}. */ - fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) { + fun startMoveToDesktop( + taskInfo: RunningTaskInfo, + startBounds: Rect, + dragToDesktopValueAnimator: MoveToDesktopAnimator + ) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: moveToFreeform with bounds taskId=%d", + "DesktopTasksController: startMoveToDesktop taskId=%d", taskInfo.taskId ) val wct = WindowContainerTransaction() + exitSplitIfApplicable(wct, taskInfo) moveHomeTaskToFront(wct) - addMoveToDesktopChanges(wct, taskInfo.getToken()) + addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, startBounds) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startTransition( - Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback) + enterDesktopTaskTransitionHandler.startMoveToDesktop(wct, dragToDesktopValueAnimator, + mOnAnimationFinishedCallback) } else { shellTaskOrganizer.applyTransaction(wct) } } - /** Brings apps to front and sets freeform task bounds */ - private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) { + /** + * The second part of the animated move to desktop transition, called after + * {@link startMoveToDesktop}. Brings apps to front and sets freeform task bounds. + */ + private fun finalizeMoveToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: moveToDesktop with animation taskId=%d", + "DesktopTasksController: finalizeMoveToDesktop taskId=%d", taskInfo.taskId ) val wct = WindowContainerTransaction() bringDesktopAppsToFront(taskInfo.displayId, wct) - addMoveToDesktopChanges(wct, taskInfo.getToken()) + addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, freeformBounds) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startTransition( - Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback) + enterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, + mOnAnimationFinishedCallback) } else { shellTaskOrganizer.applyTransaction(wct) releaseVisualIndicator() } } + /** + * Perform needed cleanup transaction once animation is complete. Bounds need to be set + * here instead of initial wct to both avoid flicker and to have task bounds to use for + * the staging animation. + * + * @param taskInfo task entering split that requires a bounds update + */ + fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) { + val wct = WindowContainerTransaction() + wct.setBounds(taskInfo.token, Rect()) + shellTaskOrganizer.applyTransaction(wct) + } + /** Move a task with given `taskId` to fullscreen */ fun moveToFullscreen(taskId: Int) { shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) } @@ -204,7 +305,25 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task.token) + addMoveToFullscreenChanges(wct, task) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) + } else { + shellTaskOrganizer.applyTransaction(wct) + } + } + + /** Move a desktop app to split screen. */ + fun moveToSplit(task: RunningTaskInfo) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: moveToSplit taskId=%d", + task.taskId + ) + val wct = WindowContainerTransaction() + wct.setWindowingMode(task.token, WINDOWING_MODE_MULTI_WINDOW) + wct.setBounds(task.token, Rect()) + wct.setDensityDpi(task.token, getDefaultDensityDpi()) if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) } else { @@ -212,22 +331,38 @@ class DesktopTasksController( } } + private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) { + if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) { + splitScreenController.prepareExitSplitScreen(wct, + splitScreenController.getStageOfTask(taskInfo.taskId), EXIT_REASON_ENTER_DESKTOP) + } + } + /** - * Move a task to fullscreen after being dragged from fullscreen and released back into - * status bar area + * The second part of the animated move to desktop transition, called after + * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen + * and released back into status bar area. */ - fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) { + fun cancelMoveToDesktop(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) { KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: cancelMoveToFreeform taskId=%d", - task.taskId + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: cancelMoveToDesktop taskId=%d", + task.taskId ) val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task.token) + wct.setBounds(task.token, Rect()) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, position, - mOnAnimationFinishedCallback) + enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, + moveToDesktopAnimator) { t -> + val callbackWCT = WindowContainerTransaction() + visualIndicator?.releaseVisualIndicator(t) + visualIndicator = null + addMoveToFullscreenChanges(callbackWCT, task) + transitions.startTransition(TRANSIT_CHANGE, callbackWCT, null /* handler */) + } } else { + addMoveToFullscreenChanges(wct, task) shellTaskOrganizer.applyTransaction(wct) releaseVisualIndicator() } @@ -240,7 +375,7 @@ class DesktopTasksController( task.taskId ) val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task.token) + addMoveToFullscreenChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { exitDesktopTaskTransitionHandler.startTransition( @@ -252,6 +387,11 @@ class DesktopTasksController( } /** Move a task to the front */ + fun moveTaskToFront(taskId: Int) { + shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveTaskToFront(task) } + } + + /** Move a task to the front */ fun moveTaskToFront(taskInfo: RunningTaskInfo) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, @@ -331,6 +471,98 @@ class DesktopTasksController( } } + /** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */ + fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) { + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return + + val stableBounds = Rect() + displayLayout.getStableBounds(stableBounds) + val destinationBounds = Rect() + if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) { + // The desktop task is currently occupying the whole stable bounds, toggle to the + // default bounds. + getDefaultDesktopTaskBounds( + density = taskInfo.configuration.densityDpi.toFloat() / DENSITY_DEFAULT, + stableBounds = stableBounds, + outBounds = destinationBounds + ) + } else { + // Toggle to the stable bounds. + destinationBounds.set(stableBounds) + } + + val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + toggleResizeDesktopTaskTransitionHandler.startTransition( + wct, + taskInfo.taskId, + windowDecor + ) + } else { + shellTaskOrganizer.applyTransaction(wct) + } + } + + /** + * Quick-resize to the right or left half of the stable bounds. + * + * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to. + */ + fun snapToHalfScreen( + taskInfo: RunningTaskInfo, + windowDecor: DesktopModeWindowDecoration, + position: SnapPosition + ) { + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return + + val stableBounds = Rect() + displayLayout.getStableBounds(stableBounds) + + val destinationWidth = stableBounds.width() / 2 + val destinationBounds = when (position) { + SnapPosition.LEFT -> { + Rect( + stableBounds.left, + stableBounds.top, + stableBounds.left + destinationWidth, + stableBounds.bottom + ) + } + SnapPosition.RIGHT -> { + Rect( + stableBounds.right - destinationWidth, + stableBounds.top, + stableBounds.right, + stableBounds.bottom + ) + } + } + + if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return + + val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + toggleResizeDesktopTaskTransitionHandler.startTransition( + wct, + taskInfo.taskId, + windowDecor + ) + } else { + shellTaskOrganizer.applyTransaction(wct) + } + } + + private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) { + val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt() + val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt() + outBounds.set(0, 0, width, height) + // Center the task in stable bounds + outBounds.offset( + stableBounds.centerX() - outBounds.centerX(), + stableBounds.centerY() - outBounds.centerY() + ) + } + /** * Get windowing move for a given `taskId` * @@ -397,87 +629,202 @@ class DesktopTasksController( transition: IBinder, request: TransitionRequestInfo ): WindowContainerTransaction? { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: handleRequest request=%s", + request + ) // Check if we should skip handling this transition + var reason = "" + val triggerTask = request.triggerTask val shouldHandleRequest = when { // Only handle open or to front transitions - request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false + request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> { + reason = "transition type not handled (${request.type})" + false + } // Only handle when it is a task transition - request.triggerTask == null -> false + triggerTask == null -> { + reason = "triggerTask is null" + false + } // Only handle standard type tasks - request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> false + triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> { + reason = "activityType not handled (${triggerTask.activityType})" + false + } // Only handle fullscreen or freeform tasks - request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN && - request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> false + triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN && + triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> { + reason = "windowingMode not handled (${triggerTask.windowingMode})" + false + } // Otherwise process it else -> true } if (!shouldHandleRequest) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: skipping handleRequest reason=%s", + reason + ) return null } - val task: RunningTaskInfo = request.triggerTask - val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) + val result = triggerTask?.let { task -> + when { + // If display has tasks stashed, handle as stashed launch + desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task) + // Check if fullscreen task should be updated + task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task) + // Check if freeform task should be updated + task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task) + else -> { + null + } + } + } + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: handleRequest result=%s", + result ?: "null" + ) + return result + } + + /** + * Applies the proper surface states (rounded corners) to tasks when desktop mode is active. + * This is intended to be used when desktop mode is part of another animation but isn't, itself, + * animating. + */ + fun syncSurfaceState( + info: TransitionInfo, + finishTransaction: SurfaceControl.Transaction + ) { + // Add rounded corners to freeform windows + val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) + info.changes + .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM } + .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) } + } - // Check if we should switch a fullscreen task to freeform - if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) { - // If there are any visible desktop tasks, switch the task to freeform - if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { - KtProtoLog.d( + private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch") + val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) + if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { + KtProtoLog.d( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: switch fullscreen task to freeform on transition" + - " taskId=%d", + "DesktopTasksController: switch freeform task to fullscreen oon transition" + + " taskId=%d", task.taskId - ) - return WindowContainerTransaction().also { wct -> - addMoveToDesktopChanges(wct, task.token) - } + ) + return WindowContainerTransaction().also { wct -> + addMoveToFullscreenChanges(wct, task) } } + return null + } - // CHeck if we should switch a freeform task to fullscreen - if (task.windowingMode == WINDOWING_MODE_FREEFORM) { - // If no visible desktop tasks, switch this task to freeform as the transition came - // outside of this controller - if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { - KtProtoLog.d( + private fun handleFullscreenTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch") + val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) + if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { + KtProtoLog.d( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: switch freeform task to fullscreen oon transition" + - " taskId=%d", + "DesktopTasksController: switch fullscreen task to freeform on transition" + + " taskId=%d", task.taskId - ) - return WindowContainerTransaction().also { wct -> - addMoveToFullscreenChanges(wct, task.token) - } + ) + return WindowContainerTransaction().also { wct -> + addMoveToDesktopChanges(wct, task) } } return null } + private fun handleStashedTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: launch apps with stashed on transition taskId=%d", + task.taskId + ) + val wct = WindowContainerTransaction() + bringDesktopAppsToFront(task.displayId, wct) + addMoveToDesktopChanges(wct, task) + desktopModeTaskRepository.setStashed(task.displayId, false) + return wct + } + private fun addMoveToDesktopChanges( wct: WindowContainerTransaction, - token: WindowContainerToken + taskInfo: RunningTaskInfo ) { - wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM) - wct.reorder(token, true /* onTop */) + val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode + val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) { + // Display windowing is freeform, set to undefined and inherit it + WINDOWING_MODE_UNDEFINED + } else { + WINDOWING_MODE_FREEFORM + } + wct.setWindowingMode(taskInfo.token, targetWindowingMode) + wct.reorder(taskInfo.token, true /* onTop */) if (isDesktopDensityOverrideSet()) { - wct.setDensityDpi(token, getDesktopDensityDpi()) + wct.setDensityDpi(taskInfo.token, getDesktopDensityDpi()) } } private fun addMoveToFullscreenChanges( wct: WindowContainerTransaction, - token: WindowContainerToken + taskInfo: RunningTaskInfo ) { - wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN) - wct.setBounds(token, null) + val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode + val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) { + // Display windowing is fullscreen, set to undefined and inherit it + WINDOWING_MODE_UNDEFINED + } else { + WINDOWING_MODE_FULLSCREEN + } + wct.setWindowingMode(taskInfo.token, targetWindowingMode) + wct.setBounds(taskInfo.token, Rect()) if (isDesktopDensityOverrideSet()) { - wct.setDensityDpi(token, getFullscreenDensityDpi()) + wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi()) + } + } + + /** + * Adds split screen changes to a transaction. Note that bounds are not reset here due to + * animation; see {@link onDesktopSplitSelectAnimComplete} + */ + private fun addMoveToSplitChanges( + wct: WindowContainerTransaction, + taskInfo: RunningTaskInfo + ) { + wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW) + // The task's density may have been overridden in freeform; revert it here as we don't + // want it overridden in multi-window. + wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi()) + } + + /** + * Requests a task be transitioned from desktop to split select. Applies needed windowing + * changes if this transition is enabled. + */ + fun requestSplit( + taskInfo: RunningTaskInfo + ) { + val windowingMode = taskInfo.windowingMode + if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM + ) { + val wct = WindowContainerTransaction() + addMoveToSplitChanges(wct, taskInfo) + splitScreenController.requestEnterSplitSelect(taskInfo, wct, + SPLIT_POSITION_BOTTOM_OR_RIGHT, taskInfo.configuration.windowConfiguration.bounds) } } - private fun getFullscreenDensityDpi(): Int { + private fun getDefaultDensityDpi(): Int { return context.resources.displayMetrics.densityDpi } @@ -497,26 +844,36 @@ class DesktopTasksController( /** * Perform checks required on drag move. Create/release fullscreen indicator as needed. + * Different sources for x and y coordinates are used due to different needs for each: + * We want split transitions to be based on input coordinates but fullscreen transition + * to be based on task edge coordinate. * * @param taskInfo the task being dragged. * @param taskSurface SurfaceControl of dragged task. - * @param y coordinate of dragged task. Used for checks against status bar height. + * @param inputCoordinate coordinates of input. Used for checks against left/right edge of screen. + * @param taskBounds bounds of dragged task. Used for checks against status bar height. */ fun onDragPositioningMove( - taskInfo: RunningTaskInfo, - taskSurface: SurfaceControl, - y: Float + taskInfo: RunningTaskInfo, + taskSurface: SurfaceControl, + inputCoordinate: PointF, + taskBounds: Rect ) { - if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { - val statusBarHeight = getStatusBarHeight(taskInfo) - if (y <= statusBarHeight && visualIndicator == null) { - visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, - displayController, context, taskSurface, shellTaskOrganizer, - rootTaskDisplayAreaOrganizer) - visualIndicator?.createFullscreenIndicatorWithAnimatedBounds() - } else if (y > statusBarHeight && visualIndicator != null) { - releaseVisualIndicator() - } + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return + if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return + var type = DesktopModeVisualIndicator.determineIndicatorType(inputCoordinate, + taskBounds, displayLayout, context) + if (type != DesktopModeVisualIndicator.INVALID_INDICATOR && visualIndicator == null) { + visualIndicator = DesktopModeVisualIndicator( + syncQueue, taskInfo, + displayController, context, taskSurface, shellTaskOrganizer, + rootTaskDisplayAreaOrganizer, type) + visualIndicator?.createIndicatorWithAnimatedBounds() + return + } + if (visualIndicator?.eventOutsideRange(inputCoordinate.x, + taskBounds.top.toFloat()) == true) { + releaseVisualIndicator() } } @@ -524,16 +881,40 @@ class DesktopTasksController( * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area. * * @param taskInfo the task being dragged. - * @param position position of surface when drag ends + * @param position position of surface when drag ends. + * @param inputCoordinate the coordinates of the motion event + * @param taskBounds the updated bounds of the task being dragged. + * @param windowDecor the window decoration for the task being dragged */ fun onDragPositioningEnd( - taskInfo: RunningTaskInfo, - position: Point + taskInfo: RunningTaskInfo, + position: Point, + inputCoordinate: PointF, + taskBounds: Rect, + windowDecor: DesktopModeWindowDecoration ) { - val statusBarHeight = getStatusBarHeight(taskInfo) - if (position.y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) { + return + } + if (taskBounds.top <= transitionAreaHeight) { + windowDecor.incrementRelayoutBlock() moveToFullscreenWithAnimation(taskInfo, position) } + if (inputCoordinate.x <= transitionAreaWidth) { + releaseVisualIndicator() + var wct = WindowContainerTransaction() + addMoveToSplitChanges(wct, taskInfo) + splitScreenController.requestEnterSplitSelect(taskInfo, wct, + SPLIT_POSITION_TOP_OR_LEFT, taskBounds) + } + if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width() + ?.minus(transitionAreaWidth) ?: return)) { + releaseVisualIndicator() + var wct = WindowContainerTransaction() + addMoveToSplitChanges(wct, taskInfo) + splitScreenController.requestEnterSplitSelect(taskInfo, wct, + SPLIT_POSITION_BOTTOM_OR_RIGHT, taskBounds) + } } /** @@ -549,16 +930,16 @@ class DesktopTasksController( taskSurface: SurfaceControl, y: Float ) { - // If the motion event is above the status bar, return since we do not need to show the - // visual indicator at this point. - if (y < getStatusBarHeight(taskInfo)) { + // If the motion event is above the status bar and the visual indicator is not yet visible, + // return since we do not need to show the visual indicator at this point. + if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) { return } if (visualIndicator == null) { visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController, context, taskSurface, shellTaskOrganizer, - rootTaskDisplayAreaOrganizer) - visualIndicator?.createFullscreenIndicator() + rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR) + visualIndicator?.createIndicatorWithAnimatedBounds() } val indicator = visualIndicator ?: return if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) { @@ -580,7 +961,7 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, freeformBounds: Rect ) { - moveToDesktopWithAnimation(taskInfo, freeformBounds) + finalizeMoveToDesktop(taskInfo, freeformBounds) } private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int { @@ -596,17 +977,17 @@ class DesktopTasksController( } /** - * Update the corner region for a specified task + * Update the exclusion region for a specified task */ - fun onTaskCornersChanged(taskId: Int, corner: Region) { - desktopModeTaskRepository.updateTaskCorners(taskId, corner) + fun onExclusionRegionChanged(taskId: Int, exclusionRegion: Region) { + desktopModeTaskRepository.updateTaskExclusionRegions(taskId, exclusionRegion) } /** - * Remove a previously tracked corner region for a specified task. + * Remove a previously tracked exclusion region for a specified task. */ - fun removeCornersForTask(taskId: Int) { - desktopModeTaskRepository.removeTaskCorners(taskId) + fun removeExclusionRegionForTask(taskId: Int) { + desktopModeTaskRepository.removeExclusionRegion(taskId) } /** @@ -620,16 +1001,22 @@ class DesktopTasksController( } /** - * Adds a listener to track changes to desktop task corners + * Adds a listener to track changes to desktop task gesture exclusion regions * * @param listener the listener to add. * @param callbackExecutor the executor to call the listener on. */ - fun setTaskCornerListener( + fun setTaskRegionListener( listener: Consumer<Region>, callbackExecutor: Executor ) { - desktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor) + desktopModeTaskRepository.setExclusionRegionListener(listener, callbackExecutor) + } + + private fun dump(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + pw.println("${prefix}DesktopTasksController") + desktopModeTaskRepository.dump(pw, innerPrefix) } /** The interface for calls from outside the shell, within the host process. */ @@ -649,7 +1036,7 @@ class DesktopTasksController( callbackExecutor: Executor ) { mainExecutor.execute { - this@DesktopTasksController.setTaskCornerListener(listener, callbackExecutor) + this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor) } } } @@ -658,8 +1045,51 @@ class DesktopTasksController( @BinderThread private class IDesktopModeImpl(private var controller: DesktopTasksController?) : IDesktopMode.Stub(), ExternalInterfaceBinder { + + private lateinit var remoteListener: + SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener> + + private val listener: VisibleTasksListener = object : VisibleTasksListener { + override fun onVisibilityChanged(displayId: Int, visible: Boolean) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: onVisibilityChanged display=%d visible=%b", + displayId, + visible + ) + remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) } + } + + override fun onStashedChanged(displayId: Int, stashed: Boolean) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: onStashedChanged display=%d stashed=%b", + displayId, + stashed + ) + remoteListener.call { l -> l.onStashedChanged(displayId, stashed) } + } + } + + init { + remoteListener = + SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>( + controller, + { c -> + c.desktopModeTaskRepository.addVisibleTasksListener( + listener, + c.mainExecutor + ) + }, + { c -> + c.desktopModeTaskRepository.removeVisibleTasksListener(listener) + } + ) + } + /** Invalidates this instance, preventing future calls from updating the controller. */ override fun invalidate() { + remoteListener.unregister() controller = null } @@ -670,6 +1100,27 @@ class DesktopTasksController( ) { c -> c.showDesktopApps(displayId) } } + override fun stashDesktopApps(displayId: Int) { + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "stashDesktopApps" + ) { c -> c.stashDesktopApps(displayId) } + } + + override fun hideStashedDesktopApps(displayId: Int) { + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "hideStashedDesktopApps" + ) { c -> c.hideStashedDesktopApps(displayId) } + } + + override fun showDesktopApp(taskId: Int) { + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "showDesktopApp" + ) { c -> c.moveTaskToFront(taskId) } + } + override fun getVisibleTaskCount(displayId: Int): Int { val result = IntArray(1) ExecutorUtils.executeRemoteCallWithTaskPermission( @@ -680,13 +1131,40 @@ class DesktopTasksController( ) return result[0] } + + override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) { + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "onDesktopSplitSelectAnimComplete" + ) { c -> c.onDesktopSplitSelectAnimComplete(taskInfo) } + } + + override fun setTaskListener(listener: IDesktopTaskListener?) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: set task listener=%s", + listener ?: "null" + ) + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "setTaskListener" + ) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() } + } } companion object { private val DESKTOP_DENSITY_OVERRIDE = - SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0) + SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284) private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000) + // Override default freeform task width when desktop mode is enabled. In dips. + private val DESKTOP_MODE_DEFAULT_WIDTH_DP = + SystemProperties.getInt("persist.wm.debug.desktop_mode.default_width", 840) + + // Override default freeform task height when desktop mode is enabled. In dips. + private val DESKTOP_MODE_DEFAULT_HEIGHT_DP = + SystemProperties.getInt("persist.wm.debug.desktop_mode.default_height", 630) + /** * Check if desktop density override is enabled */ @@ -695,4 +1173,7 @@ class DesktopTasksController( return DESKTOP_DENSITY_OVERRIDE in DESKTOP_DENSITY_ALLOWED_RANGE } } + + /** The positions on a screen that a task can snap to. */ + enum class SnapPosition { RIGHT, LEFT } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 3733b919e366..024465b281b8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -17,15 +17,16 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.app.ActivityManager; -import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.os.IBinder; +import android.util.Slog; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; @@ -36,6 +37,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration; +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator; import java.util.ArrayList; import java.util.List; @@ -48,18 +51,18 @@ import java.util.function.Supplier; */ public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler { + private static final String TAG = "EnterDesktopTaskTransitionHandler"; private final Transitions mTransitions; private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; - // The size of the screen during drag relative to the fullscreen size - public static final float DRAG_FREEFORM_SCALE = 0.4f; // The size of the screen after drag relative to the fullscreen size public static final float FINAL_FREEFORM_SCALE = 0.6f; public static final int FREEFORM_ANIMATION_DURATION = 336; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); - private Point mPosition; private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback; + private MoveToDesktopAnimator mMoveToDesktopAnimator; + private DesktopModeWindowDecoration mDesktopModeWindowDecoration; public EnterDesktopTaskTransitionHandler( Transitions transitions) { @@ -79,7 +82,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition * @param wct WindowContainerTransaction for transition * @param onAnimationEndCallback to be called after animation */ - public void startTransition(@WindowManager.TransitionType int type, + private void startTransition(@WindowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { mOnAnimationFinishedCallback = onAnimationEndCallback; @@ -88,19 +91,58 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } /** + * Starts Transition of type TRANSIT_START_DRAG_TO_DESKTOP_MODE + * @param wct WindowContainerTransaction for transition + * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move + * to desktop animation + * @param onAnimationEndCallback to be called after animation + */ + public void startMoveToDesktop(@NonNull WindowContainerTransaction wct, + @NonNull MoveToDesktopAnimator moveToDesktopAnimator, + Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { + mMoveToDesktopAnimator = moveToDesktopAnimator; + startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct, + onAnimationEndCallback); + } + + /** + * Starts Transition of type TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE + * @param wct WindowContainerTransaction for transition + * @param onAnimationEndCallback to be called after animation + */ + public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct, + Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { + startTransition(Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, wct, + onAnimationEndCallback); + } + + /** * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE * @param wct WindowContainerTransaction for transition - * @param position Position of task when transition is triggered + * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move + * to desktop animation * @param onAnimationEndCallback to be called after animation */ public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct, - Point position, + MoveToDesktopAnimator moveToDesktopAnimator, Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { - mPosition = position; - startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct, + mMoveToDesktopAnimator = moveToDesktopAnimator; + startTransition(Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE, wct, onAnimationEndCallback); } + /** + * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP + * @param wct WindowContainerTransaction for transition + * @param decor {@link DesktopModeWindowDecoration} of task being animated + */ + public void moveToDesktop(@NonNull WindowContainerTransaction wct, + DesktopModeWindowDecoration decor) { + mDesktopModeWindowDecoration = decor; + startTransition(Transitions.TRANSIT_MOVE_TO_DESKTOP, wct, + null /* onAnimationEndCallback */); + } + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @@ -140,103 +182,206 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (type == Transitions.TRANSIT_ENTER_FREEFORM + if (type == Transitions.TRANSIT_MOVE_TO_DESKTOP && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - // Transitioning to freeform but keeping fullscreen bounds, so the crop is set - // to null and we don't require an animation - final SurfaceControl sc = change.getLeash(); - startT.setWindowCrop(sc, null); - startT.apply(); - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); - return true; + return animateMoveToDesktop(change, startT, finishCallback); } - Rect endBounds = change.getEndAbsBounds(); - if (type == Transitions.TRANSIT_ENTER_DESKTOP_MODE + if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE + && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + return animateStartDragToDesktopMode(change, startT, finishT, finishCallback); + } + + final Rect endBounds = change.getEndAbsBounds(); + if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM && !endBounds.isEmpty()) { - // This Transition animates a task to freeform bounds after being dragged into freeform - // mode and brings the remaining freeform tasks to front - final SurfaceControl sc = change.getLeash(); - startT.setWindowCrop(sc, endBounds.width(), - endBounds.height()); - startT.apply(); - - // We want to find the scale of the current bounds relative to the end bounds. The - // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be - // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to - // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds - final ValueAnimator animator = - ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); - animator.setDuration(FREEFORM_ANIMATION_DURATION); - final SurfaceControl.Transaction t = mTransactionSupplier.get(); - animator.addUpdateListener(animation -> { - final float animationValue = (float) animation.getAnimatedValue(); - t.setScale(sc, animationValue, animationValue); - - final float animationWidth = endBounds.width() * animationValue; - final float animationHeight = endBounds.height() * animationValue; - final int animationX = endBounds.centerX() - (int) (animationWidth / 2); - final int animationY = endBounds.centerY() - (int) (animationHeight / 2); - - t.setPosition(sc, animationX, animationY); - t.apply(); - }); - - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mOnAnimationFinishedCallback != null) { - mOnAnimationFinishedCallback.accept(finishT); - } - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); - } - }); + return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback, + endBounds); + } - animator.start(); - return true; + if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE + && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback, + endBounds); } - if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE - && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN - && mPosition != null) { - // This Transition animates a task to fullscreen after being dragged from the status - // bar and then released back into the status bar area - final SurfaceControl sc = change.getLeash(); - // Hide the first (fullscreen) frame because the animation will start from the smaller - // scale size. - startT.hide(sc) - .setWindowCrop(sc, endBounds.width(), endBounds.height()) - .apply(); + return false; + } - final ValueAnimator animator = new ValueAnimator(); - animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f); - animator.setDuration(FREEFORM_ANIMATION_DURATION); - final SurfaceControl.Transaction t = mTransactionSupplier.get(); - animator.addUpdateListener(animation -> { - final float scale = (float) animation.getAnimatedValue(); - t.setPosition(sc, mPosition.x * (1 - scale), mPosition.y * (1 - scale)) - .setScale(sc, scale, scale) - .show(sc) - .apply(); - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mOnAnimationFinishedCallback != null) { - mOnAnimationFinishedCallback.accept(finishT); - } - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + private boolean animateMoveToDesktop( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mDesktopModeWindowDecoration == null) { + Slog.e(TAG, "Window Decoration is not available for this transition"); + return false; + } + + final SurfaceControl leash = change.getLeash(); + final Rect startBounds = change.getStartAbsBounds(); + startT.setPosition(leash, startBounds.left, startBounds.right) + .setWindowCrop(leash, startBounds.width(), startBounds.height()) + .show(leash); + mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds); + + final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(), + change.getStartAbsBounds(), change.getEndAbsBounds()); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + SurfaceControl.Transaction t = mTransactionSupplier.get(); + animator.addUpdateListener(animation -> { + final Rect animationValue = (Rect) animator.getAnimatedValue(); + t.setPosition(leash, animationValue.left, animationValue.right) + .setWindowCrop(leash, animationValue.width(), animationValue.height()) + .show(leash); + mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mDesktopModeWindowDecoration.hideResizeVeil(); + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null)); + } + }); + animator.start(); + return true; + } + + private boolean animateStartDragToDesktopMode( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + // Transitioning to freeform but keeping fullscreen bounds, so the crop is set + // to null and we don't require an animation + final SurfaceControl sc = change.getLeash(); + startT.setWindowCrop(sc, null); + + if (mMoveToDesktopAnimator == null + || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { + Slog.e(TAG, "No animator available for this transition"); + return false; + } + + // Calculate and set position of the task + final PointF position = mMoveToDesktopAnimator.getPosition(); + startT.setPosition(sc, position.x, position.y); + finishT.setPosition(sc, position.x, position.y); + + startT.apply(); + + mTransitions.getMainExecutor().execute(() -> finishCallback.onTransitionFinished(null)); + + return true; + } + + private boolean animateFinalizeDragToDesktopMode( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull Rect endBounds) { + // This Transition animates a task to freeform bounds after being dragged into freeform + // mode and brings the remaining freeform tasks to front + final SurfaceControl sc = change.getLeash(); + startT.setWindowCrop(sc, endBounds.width(), + endBounds.height()); + startT.apply(); + + // End the animation that shrinks the window when task is first dragged from fullscreen + if (mMoveToDesktopAnimator != null) { + mMoveToDesktopAnimator.endAnimator(); + } + + // We want to find the scale of the current bounds relative to the end bounds. The + // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be + // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to + // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds + final ValueAnimator animator = + ValueAnimator.ofFloat( + MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + animator.addUpdateListener(animation -> { + final float animationValue = (float) animation.getAnimatedValue(); + t.setScale(sc, animationValue, animationValue); + + final float animationWidth = endBounds.width() * animationValue; + final float animationHeight = endBounds.height() * animationValue; + final int animationX = endBounds.centerX() - (int) (animationWidth / 2); + final int animationY = endBounds.centerY() - (int) (animationHeight / 2); + + t.setPosition(sc, animationX, animationY); + t.apply(); + }); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mOnAnimationFinishedCallback != null) { + mOnAnimationFinishedCallback.accept(finishT); } - }); - animator.start(); - return true; + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null)); + } + }); + + animator.start(); + return true; + } + private boolean animateCancelDragToDesktopMode( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull Rect endBounds) { + // This Transition animates a task to fullscreen after being dragged from the status + // bar and then released back into the status bar area + final SurfaceControl sc = change.getLeash(); + // Hide the first (fullscreen) frame because the animation will start from the smaller + // scale size. + startT.hide(sc) + .setWindowCrop(sc, endBounds.width(), endBounds.height()) + .apply(); + + if (mMoveToDesktopAnimator == null + || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { + Slog.e(TAG, "No animator available for this transition"); + return false; } - return false; + // End the animation that shrinks the window when task is first dragged from fullscreen + mMoveToDesktopAnimator.endAnimator(); + + final ValueAnimator animator = new ValueAnimator(); + animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + + // Get position of the task + final float x = mMoveToDesktopAnimator.getPosition().x; + final float y = mMoveToDesktopAnimator.getPosition().y; + + animator.addUpdateListener(animation -> { + final float scale = (float) animation.getAnimatedValue(); + t.setPosition(sc, x * (1 - scale), y * (1 - scale)) + .setScale(sc, scale, scale) + .show(sc) + .apply(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mOnAnimationFinishedCallback != null) { + mOnAnimationFinishedCallback.accept(finishT); + } + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null)); + } + }); + animator.start(); + return true; } @Nullable diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java index 3ad5edf0e604..7342bd1ae5de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java @@ -168,7 +168,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH mOnAnimationFinishedCallback.accept(finishT); } mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + () -> finishCallback.onTransitionFinished(null)); } }); animator.start(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 899d67267e69..47edfd455f5a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -16,6 +16,9 @@ package com.android.wm.shell.desktopmode; +import android.app.ActivityManager.RunningTaskInfo; +import com.android.wm.shell.desktopmode.IDesktopTaskListener; + /** * Interface that is exposed to remote callers to manipulate desktop mode features. */ @@ -24,6 +27,21 @@ interface IDesktopMode { /** Show apps on the desktop on the given display */ void showDesktopApps(int displayId); + /** Stash apps on the desktop to allow launching another app from home screen */ + void stashDesktopApps(int displayId); + + /** Hide apps that may be stashed */ + void hideStashedDesktopApps(int displayId); + + /** Bring task with the given id to front */ + oneway void showDesktopApp(int taskId); + /** Get count of visible desktop tasks on the given display */ int getVisibleTaskCount(int displayId); + + /** Perform cleanup transactions after the animation to split select is complete */ + oneway void onDesktopSplitSelectAnimComplete(in RunningTaskInfo taskInfo); + + /** Set listener that will receive callbacks about updates to desktop tasks */ + oneway void setTaskListener(IDesktopTaskListener listener); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl index 62fa7b4516c7..39128a863ec9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl @@ -14,14 +14,17 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.bubble +package com.android.wm.shell.desktopmode; -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import org.junit.runner.RunWith -import org.junit.runners.Parameterized +/** + * Allows external processes to register a listener in WMShell to get updates about desktop task + * state. + */ +interface IDesktopTaskListener { + + /** Desktop task visibility has change. Visible if at least 1 task is visible. */ + oneway void onVisibilityChanged(int displayId, boolean visible); -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class DragToDismissBubbleScreenTestCfArm(flicker: FlickerTest) : - DragToDismissBubbleScreenTest(flicker) + /** Desktop task stashed status has changed. */ + oneway void onStashedChanged(int displayId, boolean stashed); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt new file mode 100644 index 000000000000..9debb25ff296 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt @@ -0,0 +1,154 @@ +/* + * 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.desktopmode + +import android.animation.Animator +import android.animation.RectEvaluator +import android.animation.ValueAnimator +import android.graphics.Rect +import android.os.IBinder +import android.util.SparseArray +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_CHANGE +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import android.window.WindowContainerTransaction +import androidx.core.animation.addListener +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration +import java.util.function.Supplier + +/** Handles the animation of quick resizing of desktop tasks. */ +class ToggleResizeDesktopTaskTransitionHandler( + private val transitions: Transitions, + private val transactionSupplier: Supplier<SurfaceControl.Transaction> +) : Transitions.TransitionHandler { + + private val rectEvaluator = RectEvaluator(Rect()) + private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>() + + private var boundsAnimator: Animator? = null + + constructor( + transitions: Transitions + ) : this(transitions, Supplier { SurfaceControl.Transaction() }) + + /** Starts a quick resize transition. */ + fun startTransition( + wct: WindowContainerTransaction, + taskId: Int, + windowDecoration: DesktopModeWindowDecoration + ) { + // Pause relayout until the transition animation finishes. + windowDecoration.incrementRelayoutBlock() + transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this) + taskToDecorationMap.put(taskId, windowDecoration) + } + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback + ): Boolean { + val change = findRelevantChange(info) + val leash = change.leash + val taskId = checkNotNull(change.taskInfo).taskId + val startBounds = change.startAbsBounds + val endBounds = change.endAbsBounds + val windowDecor = + taskToDecorationMap.removeReturnOld(taskId) + ?: throw IllegalStateException("Window decoration not found for task $taskId") + + val tx = transactionSupplier.get() + boundsAnimator?.cancel() + boundsAnimator = + ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds) + .setDuration(RESIZE_DURATION_MS) + .apply { + addListener( + onStart = { + startTransaction + .setPosition( + leash, + startBounds.left.toFloat(), + startBounds.top.toFloat() + ) + .setWindowCrop(leash, startBounds.width(), startBounds.height()) + .show(leash) + windowDecor.showResizeVeil(startTransaction, startBounds) + }, + onEnd = { + finishTransaction + .setPosition( + leash, + endBounds.left.toFloat(), + endBounds.top.toFloat() + ) + .setWindowCrop(leash, endBounds.width(), endBounds.height()) + .show(leash) + windowDecor.hideResizeVeil() + finishCallback.onTransitionFinished(null) + boundsAnimator = null + } + ) + addUpdateListener { anim -> + val rect = anim.animatedValue as Rect + tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat()) + .setWindowCrop(leash, rect.width(), rect.height()) + .show(leash) + windowDecor.updateResizeVeil(tx, rect) + } + start() + } + return true + } + + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo + ): WindowContainerTransaction? { + return null + } + + private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change { + val matchingChanges = + info.changes.filter { c -> + !isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE + } + if (matchingChanges.size != 1) { + throw IllegalStateException( + "Expected 1 relevant change but found: ${matchingChanges.size}" + ) + } + return matchingChanges.first() + } + + private fun isWallpaper(change: TransitionInfo.Change): Boolean { + return (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0 + } + + private fun isValidTaskChange(change: TransitionInfo.Change): Boolean { + return change.taskInfo != null && change.taskInfo?.taskId != -1 + } + + companion object { + private const val RESIZE_DURATION_MS = 300L + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md index 99922fbc2d95..f9ea1d4e2a07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md @@ -21,11 +21,17 @@ This code itself will not compile by itself, but the `protologtool` will preproc building to check the log state (is enabled) before printing the print format style log. **Notes** -- ProtoLogs currently only work from soong builds (ie. via make/mp). We need to reimplement the - tool for use with SysUI-studio +- ProtoLogs are only fully supported from soong builds (ie. via make/mp). In SysUI-studio it falls + back to log via Logcat - Non-text ProtoLogs are not currently supported with the Shell library (you can't view them with traces in Winscope) +### Kotlin + +Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)). +For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt) +class which has a similar API to the Java ProtoLog class. + ### Enabling ProtoLog command line logging Run these commands to enable protologs for both WM Core and WM Shell to print to logcat. ```shell diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index be2489da3628..0bf8ec32c6c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -16,9 +16,6 @@ package com.android.wm.shell.draganddrop; -import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; -import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; -import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DragEvent.ACTION_DRAG_ENDED; import static android.view.DragEvent.ACTION_DRAG_ENTERED; @@ -38,6 +35,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP; +import android.app.ActivityTaskManager; import android.content.ClipDescription; import android.content.ComponentCallbacks2; import android.content.Context; @@ -205,8 +203,6 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll final Context context = mDisplayController.getDisplayContext(displayId) .createWindowContext(TYPE_APPLICATION_OVERLAY, null); final WindowManager wm = context.getSystemService(WindowManager.class); - - // TODO(b/169894807): Figure out the right layer for this, needs to be below the task bar final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, TYPE_APPLICATION_OVERLAY, @@ -279,15 +275,11 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll } if (event.getAction() == ACTION_DRAG_STARTED) { - final boolean hasValidClipData = event.getClipData().getItemCount() > 0 - && (description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY) - || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT) - || description.hasMimeType(MIMETYPE_APPLICATION_TASK)); - pd.isHandlingDrag = hasValidClipData; + pd.isHandlingDrag = DragUtils.canHandleDrag(event); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s", pd.isHandlingDrag, event.getClipData().getItemCount(), - getMimeTypes(description)); + DragUtils.getMimeTypesConcatenated(description)); } if (!pd.isHandlingDrag) { @@ -300,10 +292,13 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll Slog.w(TAG, "Unexpected drag start during an active drag"); return false; } + // TODO(b/290391688): Also update the session data with task stack changes InstanceId loggerSessionId = mLogger.logStart(event); pd.activeDragCount++; - pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId), - event.getClipData(), loggerSessionId); + pd.dragSession = new DragSession(mContext, ActivityTaskManager.getInstance(), + mDisplayController.getDisplayLayout(displayId), event.getClipData()); + pd.dragSession.update(); + pd.dragLayout.prepare(pd.dragSession, loggerSessionId); setDropTargetWindowVisibility(pd, View.VISIBLE); notifyDragStarted(); break; @@ -324,7 +319,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll break; } case ACTION_DRAG_ENDED: - // TODO(b/169894807): Ensure sure it's not possible to get ENDED without DROP + // TODO(b/290391688): Ensure sure it's not possible to get ENDED without DROP // or EXITED if (pd.dragLayout.hasDropped()) { mLogger.logDrop(); @@ -362,17 +357,6 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll pd.setWindowVisibility(visibility); } - private String getMimeTypes(ClipDescription description) { - String mimeTypes = ""; - for (int i = 0; i < description.getMimeTypeCount(); i++) { - if (i > 0) { - mimeTypes += ", "; - } - mimeTypes += description.getMimeType(i); - } - return mimeTypes; - } - /** * Returns if any displays are currently ready to handle a drag/drop. */ @@ -462,6 +446,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll // A count of the number of active drags in progress to ensure that we only hide the window // when all the drag animations have completed int activeDragCount; + // The active drag session + DragSession dragSession; PerDisplay(int dispId, Context c, WindowManager w, FrameLayout rv, DragLayout dl) { displayId = dispId; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index fb08c878837a..e70768b6b752 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -21,7 +21,6 @@ import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVIT import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS; import static android.content.ClipDescription.EXTRA_PENDING_INTENT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; @@ -41,16 +40,13 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; -import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.PendingIntent; -import android.app.WindowConfiguration; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipDescription; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.LauncherApps; import android.graphics.Insets; import android.graphics.Rect; @@ -67,14 +63,12 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.wm.shell.R; -import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.splitscreen.SplitScreenController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.List; /** * The policy for handling drag and drop operations to shell. @@ -84,7 +78,6 @@ public class DragAndDropPolicy { private static final String TAG = DragAndDropPolicy.class.getSimpleName(); private final Context mContext; - private final ActivityTaskManager mActivityTaskManager; private final Starter mStarter; private final SplitScreenController mSplitScreen; private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>(); @@ -94,14 +87,12 @@ public class DragAndDropPolicy { private DragSession mSession; public DragAndDropPolicy(Context context, SplitScreenController splitScreen) { - this(context, ActivityTaskManager.getInstance(), splitScreen, new DefaultStarter(context)); + this(context, splitScreen, new DefaultStarter(context)); } @VisibleForTesting - DragAndDropPolicy(Context context, ActivityTaskManager activityTaskManager, - SplitScreenController splitScreen, Starter starter) { + DragAndDropPolicy(Context context, SplitScreenController splitScreen, Starter starter) { mContext = context; - mActivityTaskManager = activityTaskManager; mSplitScreen = splitScreen; mStarter = mSplitScreen != null ? mSplitScreen : starter; } @@ -109,11 +100,9 @@ public class DragAndDropPolicy { /** * Starts a new drag session with the given initial drag data. */ - void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) { + void start(DragSession session, InstanceId loggerSessionId) { mLoggerSessionId = loggerSessionId; - mSession = new DragSession(mActivityTaskManager, displayLayout, data); - // TODO(b/169894807): Also update the session data with task stack changes - mSession.update(); + mSession = session; RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION); if (disallowHitRegion == null) { mDisallowHitRegion.setEmpty(); @@ -123,13 +112,6 @@ public class DragAndDropPolicy { } /** - * Returns the last running task. - */ - ActivityManager.RunningTaskInfo getLatestRunningTask() { - return mSession.runningTaskInfo; - } - - /** * Returns the number of targets. */ int getNumTargets() { @@ -286,49 +268,6 @@ public class DragAndDropPolicy { } /** - * Per-drag session data. - */ - private static class DragSession { - private final ActivityTaskManager mActivityTaskManager; - private final ClipData mInitialDragData; - - final DisplayLayout displayLayout; - Intent dragData; - ActivityManager.RunningTaskInfo runningTaskInfo; - @WindowConfiguration.WindowingMode - int runningTaskWinMode = WINDOWING_MODE_UNDEFINED; - @WindowConfiguration.ActivityType - int runningTaskActType = ACTIVITY_TYPE_STANDARD; - boolean dragItemSupportsSplitscreen; - - DragSession(ActivityTaskManager activityTaskManager, - DisplayLayout dispLayout, ClipData data) { - mActivityTaskManager = activityTaskManager; - mInitialDragData = data; - displayLayout = dispLayout; - } - - /** - * Updates the session data based on the current state of the system. - */ - void update() { - List<ActivityManager.RunningTaskInfo> tasks = - mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */); - if (!tasks.isEmpty()) { - final ActivityManager.RunningTaskInfo task = tasks.get(0); - runningTaskInfo = task; - runningTaskWinMode = task.getWindowingMode(); - runningTaskActType = task.getActivityType(); - } - - final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo(); - dragItemSupportsSplitscreen = info == null - || ActivityInfo.isResizeableMode(info.resizeMode); - dragData = mInitialDragData.getItemAt(0).getIntent(); - } - } - - /** * Interface for actually committing the task launches. */ public interface Starter { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index fe42822ab6a1..205a455200bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -35,7 +35,6 @@ import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.StatusBarManager; -import android.content.ClipData; import android.content.Context; import android.content.res.Configuration; import android.graphics.Color; @@ -53,14 +52,13 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; -import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import java.util.ArrayList; /** - * Coordinates the visible drop targets for the current drag. + * Coordinates the visible drop targets for the current drag within a single display. */ public class DragLayout extends LinearLayout { @@ -86,6 +84,7 @@ public class DragLayout extends LinearLayout { private boolean mIsShowing; private boolean mHasDropped; + private DragSession mSession; @SuppressLint("WrongConstant") public DragLayout(Context context, SplitScreenController splitScreenController, @@ -182,16 +181,19 @@ public class DragLayout extends LinearLayout { return mHasDropped; } - public void prepare(DisplayLayout displayLayout, ClipData initialData, - InstanceId loggerSessionId) { - mPolicy.start(displayLayout, initialData, loggerSessionId); + /** + * Called when a new drag is started. + */ + public void prepare(DragSession session, InstanceId loggerSessionId) { + mPolicy.start(session, loggerSessionId); + mSession = session; mHasDropped = false; mCurrentTarget = null; boolean alreadyInSplit = mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible(); if (!alreadyInSplit) { - ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask(); + ActivityManager.RunningTaskInfo taskInfo1 = mSession.runningTaskInfo; if (taskInfo1 != null) { final int activityType = taskInfo1.getActivityType(); if (activityType == ACTIVITY_TYPE_STANDARD) { @@ -356,7 +358,16 @@ public class DragLayout extends LinearLayout { */ public void hide(DragEvent event, Runnable hideCompleteCallback) { mIsShowing = false; - animateSplitContainers(false, hideCompleteCallback); + animateSplitContainers(false, () -> { + if (hideCompleteCallback != null) { + hideCompleteCallback.run(); + } + switch (event.getAction()) { + case DragEvent.ACTION_DROP: + case DragEvent.ACTION_DRAG_ENDED: + mSession = null; + } + }); // Reset the state if we previously force-ignore the bottom margin mDropZoneView1.setForceIgnoreBottomMargin(false); mDropZoneView2.setForceIgnoreBottomMargin(false); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java new file mode 100644 index 000000000000..478b6a9d95f6 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java @@ -0,0 +1,89 @@ +/* + * 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.draganddrop; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.ClipDescription.EXTRA_PENDING_INTENT; +import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; +import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; +import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; +import static android.content.Intent.EXTRA_USER; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.PendingIntent; +import android.app.WindowConfiguration; +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.net.Uri; +import android.os.UserHandle; + +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; + +import com.android.wm.shell.common.DisplayLayout; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * Per-drag session data. + */ +public class DragSession { + private final ActivityTaskManager mActivityTaskManager; + private final ClipData mInitialDragData; + + final DisplayLayout displayLayout; + Intent dragData; + ActivityManager.RunningTaskInfo runningTaskInfo; + @WindowConfiguration.WindowingMode + int runningTaskWinMode = WINDOWING_MODE_UNDEFINED; + @WindowConfiguration.ActivityType + int runningTaskActType = ACTIVITY_TYPE_STANDARD; + boolean dragItemSupportsSplitscreen; + + DragSession(Context context, ActivityTaskManager activityTaskManager, + DisplayLayout dispLayout, ClipData data) { + mActivityTaskManager = activityTaskManager; + mInitialDragData = data; + displayLayout = dispLayout; + } + + /** + * Updates the session data based on the current state of the system. + */ + void update() { + List<ActivityManager.RunningTaskInfo> tasks = + mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */); + if (!tasks.isEmpty()) { + final ActivityManager.RunningTaskInfo task = tasks.get(0); + runningTaskInfo = task; + runningTaskWinMode = task.getWindowingMode(); + runningTaskActType = task.getActivityType(); + } + + final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo(); + dragItemSupportsSplitscreen = info == null + || ActivityInfo.isResizeableMode(info.resizeMode); + dragData = mInitialDragData.getItemAt(0).getIntent(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java new file mode 100644 index 000000000000..7c0883d2538f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java @@ -0,0 +1,60 @@ +/* + * 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.draganddrop; + +import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; +import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; +import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; + +import android.content.ClipDescription; +import android.view.DragEvent; + +/** Collection of utility classes for handling drag and drop. */ +public class DragUtils { + private static final String TAG = "DragUtils"; + + /** + * Returns whether we can handle this particular drag. + */ + public static boolean canHandleDrag(DragEvent event) { + return event.getClipData().getItemCount() > 0 + && (isAppDrag(event.getClipDescription())); + } + + /** + * Returns whether this clip data description represents an app drag. + */ + public static boolean isAppDrag(ClipDescription description) { + return description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY) + || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT) + || description.hasMimeType(MIMETYPE_APPLICATION_TASK); + } + + /** + * Returns a list of the mime types provided in the clip description. + */ + public static String getMimeTypesConcatenated(ClipDescription description) { + String mimeTypes = ""; + for (int i = 0; i < description.getMimeTypeCount(); i++) { + if (i > 0) { + mimeTypes += ", "; + } + mimeTypes += description.getMimeType(i); + } + return mimeTypes; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java deleted file mode 100644 index 73deea54e52f..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java +++ /dev/null @@ -1,165 +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.draganddrop; - -import android.animation.ObjectAnimator; -import android.animation.RectEvaluator; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.IntProperty; -import android.util.Property; -import android.view.animation.Interpolator; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.graphics.ColorUtils; -import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.R; - -/** - * Drawable to draw the region that the target will have once it is dropped. - */ -public class DropOutlineDrawable extends Drawable { - - private static final int BOUNDS_DURATION = 200; - private static final int ALPHA_DURATION = 135; - - private final IntProperty<DropOutlineDrawable> ALPHA = - new IntProperty<DropOutlineDrawable>("alpha") { - @Override - public void setValue(DropOutlineDrawable d, int alpha) { - d.setAlpha(alpha); - } - - @Override - public Integer get(DropOutlineDrawable d) { - return d.getAlpha(); - } - }; - - private final Property<DropOutlineDrawable, Rect> BOUNDS = - new Property<DropOutlineDrawable, Rect>(Rect.class, "bounds") { - @Override - public void set(DropOutlineDrawable d, Rect bounds) { - d.setRegionBounds(bounds); - } - - @Override - public Rect get(DropOutlineDrawable d) { - return d.getRegionBounds(); - } - }; - - private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); - private ObjectAnimator mBoundsAnimator; - private ObjectAnimator mAlphaAnimator; - - private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Rect mBounds = new Rect(); - private final float mCornerRadius; - private final int mMaxAlpha; - private int mColor; - - public DropOutlineDrawable(Context context) { - super(); - // TODO(b/169894807): Use corner specific radii and maybe lower radius for non-edge corners - mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); - mColor = context.getColor(R.color.drop_outline_background); - mMaxAlpha = Color.alpha(mColor); - // Initialize as hidden - ALPHA.set(this, 0); - } - - @Override - public void setColorFilter(@Nullable ColorFilter colorFilter) { - // Do nothing - } - - @Override - public void setAlpha(int alpha) { - mColor = ColorUtils.setAlphaComponent(mColor, alpha); - mPaint.setColor(mColor); - invalidateSelf(); - } - - @Override - public int getAlpha() { - return Color.alpha(mColor); - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - protected void onBoundsChange(Rect bounds) { - invalidateSelf(); - } - - @Override - public void draw(@NonNull Canvas canvas) { - canvas.drawRoundRect(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom, - mCornerRadius, mCornerRadius, mPaint); - } - - public void setRegionBounds(Rect bounds) { - mBounds.set(bounds); - invalidateSelf(); - } - - public Rect getRegionBounds() { - return mBounds; - } - - ObjectAnimator startBoundsAnimation(Rect toBounds, Interpolator interpolator) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Animate bounds: from=%s to=%s", - mBounds, toBounds); - if (mBoundsAnimator != null) { - mBoundsAnimator.cancel(); - } - mBoundsAnimator = ObjectAnimator.ofObject(this, BOUNDS, mRectEvaluator, - mBounds, toBounds); - mBoundsAnimator.setDuration(BOUNDS_DURATION); - mBoundsAnimator.setInterpolator(interpolator); - mBoundsAnimator.start(); - return mBoundsAnimator; - } - - ObjectAnimator startVisibilityAnimation(boolean visible, Interpolator interpolator) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Animate alpha: from=%d to=%d", - Color.alpha(mColor), visible ? mMaxAlpha : 0); - if (mAlphaAnimator != null) { - mAlphaAnimator.cancel(); - } - mAlphaAnimator = ObjectAnimator.ofInt(this, ALPHA, Color.alpha(mColor), - visible ? mMaxAlpha : 0); - mAlphaAnimator.setDuration(ALPHA_DURATION); - mAlphaAnimator.setInterpolator(interpolator); - mAlphaAnimator.start(); - return mAlphaAnimator; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 22541bbd892a..a80241e0ac5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -68,7 +68,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, private void onInit() { mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM); - if (DesktopModeStatus.isAnyEnabled()) { + if (DesktopModeStatus.isEnabled()) { mShellTaskOrganizer.addFocusListener(this); } } @@ -90,7 +90,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, t.apply(); } - if (DesktopModeStatus.isAnyEnabled()) { + if (DesktopModeStatus.isEnabled()) { mDesktopModeTaskRepository.ifPresent(repository -> { repository.addOrMoveFreeformTaskToTop(taskInfo.taskId); if (taskInfo.isVisible) { @@ -111,7 +111,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, taskInfo.taskId); mTasks.remove(taskInfo.taskId); - if (DesktopModeStatus.isAnyEnabled()) { + if (DesktopModeStatus.isEnabled()) { mDesktopModeTaskRepository.ifPresent(repository -> { repository.removeFreeformTask(taskInfo.taskId); if (repository.removeActiveTask(taskInfo.taskId)) { @@ -135,7 +135,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, taskInfo.taskId); mWindowDecorationViewModel.onTaskInfoChanged(taskInfo); state.mTaskInfo = taskInfo; - if (DesktopModeStatus.isAnyEnabled()) { + if (DesktopModeStatus.isEnabled()) { mDesktopModeTaskRepository.ifPresent(repository -> { if (taskInfo.isVisible) { if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) { @@ -154,7 +154,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Focus Changed: #%d focused=%b", taskInfo.taskId, taskInfo.isFocused); - if (DesktopModeStatus.isAnyEnabled() && taskInfo.isFocused) { + if (DesktopModeStatus.isEnabled() && taskInfo.isFocused) { mDesktopModeTaskRepository.ifPresent(repository -> { repository.addOrMoveFreeformTaskToTop(taskInfo.taskId); }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 04fc79acadbd..84027753435b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -19,9 +19,15 @@ package com.android.wm.shell.freeform; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.WindowConfiguration; +import android.content.Context; +import android.graphics.Rect; import android.os.IBinder; +import android.util.ArrayMap; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; @@ -31,6 +37,8 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -39,23 +47,37 @@ import java.util.ArrayList; import java.util.List; /** - * The {@link Transitions.TransitionHandler} that handles freeform task maximizing and restoring - * transitions. + * The {@link Transitions.TransitionHandler} that handles freeform task maximizing, closing, and + * restoring transitions. */ public class FreeformTaskTransitionHandler implements Transitions.TransitionHandler, FreeformTaskTransitionStarter { - + private static final int CLOSE_ANIM_DURATION = 400; + private final Context mContext; private final Transitions mTransitions; private final WindowDecorViewModel mWindowDecorViewModel; + private final DisplayController mDisplayController; + private final ShellExecutor mMainExecutor; + private final ShellExecutor mAnimExecutor; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); + private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>(); + public FreeformTaskTransitionHandler( ShellInit shellInit, Transitions transitions, - WindowDecorViewModel windowDecorViewModel) { + Context context, + WindowDecorViewModel windowDecorViewModel, + DisplayController displayController, + ShellExecutor mainExecutor, + ShellExecutor animExecutor) { mTransitions = transitions; + mContext = context; mWindowDecorViewModel = windowDecorViewModel; + mDisplayController = displayController; + mMainExecutor = mainExecutor; + mAnimExecutor = animExecutor; if (Transitions.ENABLE_SHELL_TRANSITIONS) { shellInit.addInitCallback(this::onInit, this); } @@ -103,6 +125,14 @@ public class FreeformTaskTransitionHandler @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean transitionHandled = false; + final ArrayList<Animator> animations = new ArrayList<>(); + final Runnable onAnimFinish = () -> { + if (!animations.isEmpty()) return; + mMainExecutor.execute(() -> { + mAnimations.remove(transition); + finishCallback.onTransitionFinished(null /* wct */); + }); + }; for (TransitionInfo.Change change : info.getChanges()) { if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { continue; @@ -121,21 +151,45 @@ public class FreeformTaskTransitionHandler case WindowManager.TRANSIT_TO_BACK: transitionHandled |= startMinimizeTransition(transition); break; + case WindowManager.TRANSIT_CLOSE: + if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) { + transitionHandled |= startCloseTransition(transition, change, + finishT, animations, onAnimFinish); + } + break; } } - - mPendingTransitionTokens.remove(transition); - if (!transitionHandled) { return false; } - + mAnimations.put(transition, animations); + // startT must be applied before animations start. startT.apply(); - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + mAnimExecutor.execute(() -> { + for (Animator anim : animations) { + anim.start(); + } + }); + // Run this here in case no animators are created. + onAnimFinish.run(); + mPendingTransitionTokens.remove(transition); return true; } + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ArrayList<Animator> animations = mAnimations.get(mergeTarget); + if (animations == null) return; + mAnimExecutor.execute(() -> { + for (Animator anim : animations) { + anim.end(); + } + }); + + } + private boolean startChangeTransition( IBinder transition, int type, @@ -165,6 +219,36 @@ public class FreeformTaskTransitionHandler return mPendingTransitionTokens.contains(transition); } + private boolean startCloseTransition(IBinder transition, TransitionInfo.Change change, + SurfaceControl.Transaction finishT, ArrayList<Animator> animations, + Runnable onAnimFinish) { + if (!mPendingTransitionTokens.contains(transition)) return false; + int screenHeight = mDisplayController + .getDisplayLayout(change.getTaskInfo().displayId).height(); + ValueAnimator animator = new ValueAnimator(); + animator.setDuration(CLOSE_ANIM_DURATION) + .setFloatValues(0f, 1f); + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + SurfaceControl sc = change.getLeash(); + finishT.hide(sc); + Rect startBounds = new Rect(change.getTaskInfo().configuration.windowConfiguration + .getBounds()); + animator.addUpdateListener(animation -> { + t.setPosition(sc, startBounds.left, + startBounds.top + (animation.getAnimatedFraction() * screenHeight)); + t.apply(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animations.remove(animator); + onAnimFinish.run(); + } + }); + animations.add(animator); + return true; + } + @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 56bd188a0d7d..13c0ac4bbaa7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -16,10 +16,12 @@ package com.android.wm.shell.keyguard; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; -import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING; import static android.view.WindowManager.TRANSIT_SLEEP; @@ -28,6 +30,7 @@ import static com.android.wm.shell.util.TransitionUtil.isOpeningType; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -166,10 +169,16 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler if (sct != null) { finishTransaction.merge(sct); } + final WindowContainerTransaction mergedWct = + new WindowContainerTransaction(); + if (wct != null) { + mergedWct.merge(wct, true); + } + maybeDismissFreeformOccludingKeyguard(mergedWct, info); // Post our finish callback to let startAnimation finish first. mMainExecutor.executeDelayed(() -> { mStartedTransitions.remove(transition); - finishCallback.onTransitionFinished(wct, null); + finishCallback.onTransitionFinished(mergedWct); }, 0); } }); @@ -206,7 +215,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler // implementing an AIDL interface. Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e); } - nextFinishCallback.onTransitionFinished(null, null); + nextFinishCallback.onTransitionFinished(null); } else if (nextInfo.getType() == TRANSIT_SLEEP) { // An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep // token is held. In cases where keyguard is showing, we are running the animation for @@ -261,6 +270,26 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler } } + private void maybeDismissFreeformOccludingKeyguard( + WindowContainerTransaction wct, TransitionInfo info) { + if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) == 0) { + return; + } + // There's a window occluding the Keyguard, find it and if it's in freeform mode, change it + // to fullscreen. + for (int i = 0; i < info.getChanges().size(); i++) { + final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo != null && taskInfo.taskId != INVALID_TASK_ID + && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM + && taskInfo.isFocused && change.getContainer() != null) { + wct.setWindowingMode(change.getContainer(), WINDOWING_MODE_FULLSCREEN); + wct.setBounds(change.getContainer(), null); + return; + } + } + } + private static class FakeFinishCallback extends IRemoteTransitionFinishedCallback.Stub { @Override public void onTransitionFinished( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index f34d2a827e69..a9aa6badcfe2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -67,4 +67,11 @@ public interface Pip { * view hierarchy or destroyed. */ default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { } + + /** + * @return {@link PipTransitionController} instance. + */ + default PipTransitionController getPipTransitionController() { + return null; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java deleted file mode 100644 index 48a3fc2460a2..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java +++ /dev/null @@ -1,94 +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.pip; - -import static android.app.AppOpsManager.MODE_ALLOWED; -import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; - -import android.app.AppOpsManager; -import android.app.AppOpsManager.OnOpChangedListener; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.util.Pair; - -import com.android.wm.shell.common.ShellExecutor; - -public class PipAppOpsListener { - private static final String TAG = PipAppOpsListener.class.getSimpleName(); - - private Context mContext; - private ShellExecutor mMainExecutor; - private AppOpsManager mAppOpsManager; - private Callback mCallback; - - private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() { - @Override - public void onOpChanged(String op, String packageName) { - try { - // Dismiss the PiP once the user disables the app ops setting for that package - final Pair<ComponentName, Integer> topPipActivityInfo = - PipUtils.getTopPipActivity(mContext); - if (topPipActivityInfo.first != null) { - final ApplicationInfo appInfo = mContext.getPackageManager() - .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second); - if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) && - mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, - packageName) != MODE_ALLOWED) { - mMainExecutor.execute(() -> mCallback.dismissPip()); - } - } - } catch (NameNotFoundException e) { - // Unregister the listener if the package can't be found - unregisterAppOpsListener(); - } - } - }; - - public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) { - mContext = context; - mMainExecutor = mainExecutor; - mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - mCallback = callback; - } - - public void onActivityPinned(String packageName) { - // Register for changes to the app ops setting for this package while it is in PiP - registerAppOpsListener(packageName); - } - - public void onActivityUnpinned() { - // Unregister for changes to the previously PiP'ed package - unregisterAppOpsListener(); - } - - private void registerAppOpsListener(String packageName) { - mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName, - mAppOpsChangedListener); - } - - private void unregisterAppOpsListener() { - mAppOpsManager.stopWatchingMode(mAppOpsChangedListener); - } - - /** Callback for PipAppOpsListener to request changes to the PIP window. */ - public interface Callback { - /** Dismisses the PIP window. */ - void dismissPip(); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java deleted file mode 100644 index 2590cab9ff2e..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java +++ /dev/null @@ -1,367 +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.pip; - -import static android.app.PendingIntent.FLAG_IMMUTABLE; -import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; - -import android.annotation.DrawableRes; -import android.annotation.StringRes; -import android.annotation.SuppressLint; -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.graphics.drawable.Icon; -import android.media.MediaMetadata; -import android.media.session.MediaController; -import android.media.session.MediaSession; -import android.media.session.MediaSessionManager; -import android.media.session.PlaybackState; -import android.os.Handler; -import android.os.HandlerExecutor; -import android.os.UserHandle; - -import androidx.annotation.Nullable; - -import com.android.wm.shell.R; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Interfaces with the {@link MediaSessionManager} to compose the right set of actions to show (only - * if there are no actions from the PiP activity itself). The active media controller is only set - * when there is a media session from the top PiP activity. - */ -public class PipMediaController { - private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"; - - private static final String ACTION_PLAY = "com.android.wm.shell.pip.PLAY"; - private static final String ACTION_PAUSE = "com.android.wm.shell.pip.PAUSE"; - private static final String ACTION_NEXT = "com.android.wm.shell.pip.NEXT"; - private static final String ACTION_PREV = "com.android.wm.shell.pip.PREV"; - - /** - * A listener interface to receive notification on changes to the media actions. - */ - public interface ActionListener { - /** - * Called when the media actions changed. - */ - void onMediaActionsChanged(List<RemoteAction> actions); - } - - /** - * A listener interface to receive notification on changes to the media metadata. - */ - public interface MetadataListener { - /** - * Called when the media metadata changed. - */ - void onMediaMetadataChanged(MediaMetadata metadata); - } - - /** - * A listener interface to receive notification on changes to the media session token. - */ - public interface TokenListener { - /** - * Called when the media session token changed. - */ - void onMediaSessionTokenChanged(MediaSession.Token token); - } - - private final Context mContext; - private final Handler mMainHandler; - private final HandlerExecutor mHandlerExecutor; - - private final MediaSessionManager mMediaSessionManager; - private MediaController mMediaController; - - private RemoteAction mPauseAction; - private RemoteAction mPlayAction; - private RemoteAction mNextAction; - private RemoteAction mPrevAction; - - private final BroadcastReceiver mMediaActionReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mMediaController == null || mMediaController.getTransportControls() == null) { - // no active media session, bail early. - return; - } - switch (intent.getAction()) { - case ACTION_PLAY: - mMediaController.getTransportControls().play(); - break; - case ACTION_PAUSE: - mMediaController.getTransportControls().pause(); - break; - case ACTION_NEXT: - mMediaController.getTransportControls().skipToNext(); - break; - case ACTION_PREV: - mMediaController.getTransportControls().skipToPrevious(); - break; - } - } - }; - - private final MediaController.Callback mPlaybackChangedListener = - new MediaController.Callback() { - @Override - public void onPlaybackStateChanged(PlaybackState state) { - notifyActionsChanged(); - } - - @Override - public void onMetadataChanged(@Nullable MediaMetadata metadata) { - notifyMetadataChanged(metadata); - } - }; - - private final MediaSessionManager.OnActiveSessionsChangedListener mSessionsChangedListener = - this::resolveActiveMediaController; - - private final ArrayList<ActionListener> mActionListeners = new ArrayList<>(); - private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>(); - private final ArrayList<TokenListener> mTokenListeners = new ArrayList<>(); - - public PipMediaController(Context context, Handler mainHandler) { - mContext = context; - mMainHandler = mainHandler; - mHandlerExecutor = new HandlerExecutor(mMainHandler); - IntentFilter mediaControlFilter = new IntentFilter(); - mediaControlFilter.addAction(ACTION_PLAY); - mediaControlFilter.addAction(ACTION_PAUSE); - mediaControlFilter.addAction(ACTION_NEXT); - mediaControlFilter.addAction(ACTION_PREV); - mContext.registerReceiverForAllUsers(mMediaActionReceiver, mediaControlFilter, - SYSTEMUI_PERMISSION, mainHandler, Context.RECEIVER_EXPORTED); - - // Creates the standard media buttons that we may show. - mPauseAction = getDefaultRemoteAction(R.string.pip_pause, - R.drawable.pip_ic_pause_white, ACTION_PAUSE); - mPlayAction = getDefaultRemoteAction(R.string.pip_play, - R.drawable.pip_ic_play_arrow_white, ACTION_PLAY); - mNextAction = getDefaultRemoteAction(R.string.pip_skip_to_next, - R.drawable.pip_ic_skip_next_white, ACTION_NEXT); - mPrevAction = getDefaultRemoteAction(R.string.pip_skip_to_prev, - R.drawable.pip_ic_skip_previous_white, ACTION_PREV); - - mMediaSessionManager = context.getSystemService(MediaSessionManager.class); - } - - /** - * Handles when an activity is pinned. - */ - public void onActivityPinned() { - // Once we enter PiP, try to find the active media controller for the top most activity - resolveActiveMediaController(mMediaSessionManager.getActiveSessionsForUser(null, - UserHandle.CURRENT)); - } - - /** - * Adds a new media action listener. - */ - public void addActionListener(ActionListener listener) { - if (!mActionListeners.contains(listener)) { - mActionListeners.add(listener); - listener.onMediaActionsChanged(getMediaActions()); - } - } - - /** - * Removes a media action listener. - */ - public void removeActionListener(ActionListener listener) { - listener.onMediaActionsChanged(Collections.emptyList()); - mActionListeners.remove(listener); - } - - /** - * Adds a new media metadata listener. - */ - public void addMetadataListener(MetadataListener listener) { - if (!mMetadataListeners.contains(listener)) { - mMetadataListeners.add(listener); - listener.onMediaMetadataChanged(getMediaMetadata()); - } - } - - /** - * Removes a media metadata listener. - */ - public void removeMetadataListener(MetadataListener listener) { - listener.onMediaMetadataChanged(null); - mMetadataListeners.remove(listener); - } - - /** - * Adds a new token listener. - */ - public void addTokenListener(TokenListener listener) { - if (!mTokenListeners.contains(listener)) { - mTokenListeners.add(listener); - listener.onMediaSessionTokenChanged(getToken()); - } - } - - /** - * Removes a token listener. - */ - public void removeTokenListener(TokenListener listener) { - listener.onMediaSessionTokenChanged(null); - mTokenListeners.remove(listener); - } - - private MediaSession.Token getToken() { - if (mMediaController == null) { - return null; - } - return mMediaController.getSessionToken(); - } - - private MediaMetadata getMediaMetadata() { - return mMediaController != null ? mMediaController.getMetadata() : null; - } - - /** - * Gets the set of media actions currently available. - */ - // This is due to using PlaybackState#isActive, which is added in API 31. - // It can be removed when min_sdk of the app is set to 31 or greater. - @SuppressLint("NewApi") - private List<RemoteAction> getMediaActions() { - // Cache the PlaybackState since it's a Binder call. - final PlaybackState playbackState; - if (mMediaController == null - || (playbackState = mMediaController.getPlaybackState()) == null) { - return Collections.emptyList(); - } - - ArrayList<RemoteAction> mediaActions = new ArrayList<>(); - boolean isPlaying = playbackState.isActive(); - long actions = playbackState.getActions(); - - // Prev action - mPrevAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0); - mediaActions.add(mPrevAction); - - // Play/pause action - if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) { - mediaActions.add(mPlayAction); - } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) { - mediaActions.add(mPauseAction); - } - - // Next action - mNextAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0); - mediaActions.add(mNextAction); - return mediaActions; - } - - /** @return Default {@link RemoteAction} sends broadcast back to SysUI. */ - private RemoteAction getDefaultRemoteAction(@StringRes int titleAndDescription, - @DrawableRes int icon, String action) { - final String titleAndDescriptionStr = mContext.getString(titleAndDescription); - final Intent intent = new Intent(action); - intent.setPackage(mContext.getPackageName()); - return new RemoteAction(Icon.createWithResource(mContext, icon), - titleAndDescriptionStr, titleAndDescriptionStr, - PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent, - FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE)); - } - - /** - * Re-registers the session listener for the current user. - */ - public void registerSessionListenerForCurrentUser() { - mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener); - mMediaSessionManager.addOnActiveSessionsChangedListener(null, UserHandle.CURRENT, - mHandlerExecutor, mSessionsChangedListener); - } - - /** - * Tries to find and set the active media controller for the top PiP activity. - */ - private void resolveActiveMediaController(List<MediaController> controllers) { - if (controllers != null) { - final ComponentName topActivity = PipUtils.getTopPipActivity(mContext).first; - if (topActivity != null) { - for (int i = 0; i < controllers.size(); i++) { - final MediaController controller = controllers.get(i); - if (controller.getPackageName().equals(topActivity.getPackageName())) { - setActiveMediaController(controller); - return; - } - } - } - } - setActiveMediaController(null); - } - - /** - * Sets the active media controller for the top PiP activity. - */ - private void setActiveMediaController(MediaController controller) { - if (controller != mMediaController) { - if (mMediaController != null) { - mMediaController.unregisterCallback(mPlaybackChangedListener); - } - mMediaController = controller; - if (controller != null) { - controller.registerCallback(mPlaybackChangedListener, mMainHandler); - } - notifyActionsChanged(); - notifyMetadataChanged(getMediaMetadata()); - notifyTokenChanged(getToken()); - - // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV) - } - } - - /** - * Notifies all listeners that the actions have changed. - */ - private void notifyActionsChanged() { - if (!mActionListeners.isEmpty()) { - List<RemoteAction> actions = getMediaActions(); - mActionListeners.forEach(l -> l.onMediaActionsChanged(actions)); - } - } - - /** - * Notifies all listeners that the metadata have changed. - */ - private void notifyMetadataChanged(MediaMetadata metadata) { - if (!mMetadataListeners.isEmpty()) { - mMetadataListeners.forEach(l -> l.onMediaMetadataChanged(metadata)); - } - } - - private void notifyTokenChanged(MediaSession.Token token) { - if (!mTokenListeners.isEmpty()) { - mTokenListeners.forEach(l -> l.onMediaSessionTokenChanged(token)); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 08da4857a0b0..9e8f9c68d43d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -63,7 +63,6 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; -import android.os.SystemProperties; import android.view.Choreographer; import android.view.Display; import android.view.Surface; @@ -83,6 +82,11 @@ import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -364,13 +368,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mMainExecutor = mainExecutor; // TODO: Can be removed once wm components are created on the shell-main thread - mMainExecutor.execute(() -> { - mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP); - }); - mTaskOrganizer.addFocusListener(this); - mPipTransitionController.setPipOrganizer(this); - displayController.addDisplayWindowListener(this); - pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); + if (!PipUtils.isPip2ExperimentEnabled()) { + mMainExecutor.execute(() -> { + mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP); + }); + mTaskOrganizer.addFocusListener(this); + mPipTransitionController.setPipOrganizer(this); + displayController.addDisplayWindowListener(this); + pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); + } } public PipTransitionController getTransitionController() { @@ -590,7 +596,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, SplitScreenController split = mSplitScreenOptional.get(); if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) { split.prepareExitSplitScreen(wct, split.getStageOfTask( - mTaskInfo.lastParentTaskIdBeforePip)); + mTaskInfo.lastParentTaskIdBeforePip), + SplitScreenController.EXIT_REASON_APP_FINISHED); } } mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds); @@ -1732,17 +1739,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // animation. // TODO(b/272819817): cleanup the null-check and extra logging. final boolean hasTopActivityInfo = mTaskInfo.topActivityInfo != null; - if (!hasTopActivityInfo) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, - "%s: TaskInfo.topActivityInfo is null", TAG); - } - if (SystemProperties.getBoolean( - "persist.wm.debug.enable_pip_app_icon_overlay", true) - && hasTopActivityInfo) { + if (hasTopActivityInfo) { animator.setAppIconContentOverlay( mContext, currentBounds, mTaskInfo.topActivityInfo, mPipBoundsState.getLauncherState().getAppIconSizePx()); } else { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "%s: TaskInfo.topActivityInfo is null", TAG); animator.setColorContentOverlay(mContext); } } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index e3d53fc415db..018d674e5427 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -49,7 +49,6 @@ import android.content.Context; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; -import android.os.SystemProperties; import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager; @@ -65,6 +64,10 @@ import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; @@ -89,6 +92,7 @@ public class PipTransition extends PipTransitionController { private final int mEnterExitAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private final Optional<SplitScreenController> mSplitScreenOptional; + private final PipAnimationController mPipAnimationController; private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS; private Transitions.TransitionFinishCallback mFinishCallback; private SurfaceControl.Transaction mFinishTransaction; @@ -137,10 +141,11 @@ public class PipTransition extends PipTransitionController { PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<SplitScreenController> splitScreenOptional) { super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, - pipBoundsAlgorithm, pipAnimationController); + pipBoundsAlgorithm); mContext = context; mPipTransitionState = pipTransitionState; mPipDisplayLayoutState = pipDisplayLayoutState; + mPipAnimationController = pipAnimationController; mEnterExitAnimationDuration = context.getResources() .getInteger(R.integer.config_pipResizeAnimationDuration); mSurfaceTransactionHelper = pipSurfaceTransactionHelper; @@ -148,6 +153,13 @@ public class PipTransition extends PipTransitionController { } @Override + protected void onInit() { + if (!PipUtils.isPip2ExperimentEnabled()) { + mTransitions.addHandler(this); + } + } + + @Override public void startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds) { if (destinationBounds != null) { @@ -447,7 +459,7 @@ public class PipTransition extends PipTransitionController { // handler if there is a pending PiP animation. final Transitions.TransitionFinishCallback finishCallback = mFinishCallback; mFinishCallback = null; - finishCallback.onTransitionFinished(wct, null /* callback */); + finishCallback.onTransitionFinished(wct); } @Override @@ -456,7 +468,7 @@ public class PipTransition extends PipTransitionController { // for example, when app crashes while in PiP and exit transition has not started mCurrentPipTaskToken = null; if (mFinishCallback == null) return; - mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */); + mFinishCallback.onTransitionFinished(null /* wct */); mFinishCallback = null; mFinishTransaction = null; } @@ -586,7 +598,7 @@ public class PipTransition extends PipTransitionController { final boolean useLocalLeash = activitySc != null; final boolean toFullscreen = pipChange.getEndAbsBounds().equals( mPipBoundsState.getDisplayBounds()); - mFinishCallback = (wct, wctCB) -> { + mFinishCallback = (wct) -> { mPipOrganizer.onExitPipFinished(taskInfo); // TODO(b/286346098): remove the OPEN app flicker completely @@ -610,7 +622,7 @@ public class PipTransition extends PipTransitionController { mPipAnimationController.resetAnimatorState(); finishTransaction.remove(pipLeash); } - finishCallback.onTransitionFinished(wct, wctCB); + finishCallback.onTransitionFinished(wct); }; mFinishTransaction = finishTransaction; @@ -750,7 +762,7 @@ public class PipTransition extends PipTransitionController { finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), mPipDisplayLayoutState.getDisplayBounds()); mPipOrganizer.onExitPipFinished(taskInfo); - finishCallback.onTransitionFinished(null, null); + finishCallback.onTransitionFinished(null); } /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */ @@ -902,17 +914,13 @@ public class PipTransition extends PipTransitionController { // animation. // TODO(b/272819817): cleanup the null-check and extra logging. final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null; - if (!hasTopActivityInfo) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, - "%s: TaskInfo.topActivityInfo is null", TAG); - } - if (SystemProperties.getBoolean( - "persist.wm.debug.enable_pip_app_icon_overlay", true) - && hasTopActivityInfo) { + if (hasTopActivityInfo) { animator.setAppIconContentOverlay( mContext, currentBounds, taskInfo.topActivityInfo, mPipBoundsState.getLauncherState().getAppIconSizePx()); } else { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "%s: TaskInfo.topActivityInfo is null", TAG); animator.setColorContentOverlay(mContext); } } else { @@ -1045,13 +1053,27 @@ public class PipTransition extends PipTransitionController { startTransaction.apply(); mPipOrganizer.onExitPipFinished(taskInfo); - finishCallback.onTransitionFinished(null, null); + finishCallback.onTransitionFinished(null); } private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange, @NonNull SurfaceControl.Transaction startTransaction) { final SurfaceControl leash = prevPipTaskChange.getLeash(); - startTransaction.remove(leash); + final Rect bounds = prevPipTaskChange.getEndAbsBounds(); + final Point offset = prevPipTaskChange.getEndRelOffset(); + bounds.offset(-offset.x, -offset.y); + + startTransaction.setWindowCrop(leash, null); + startTransaction.setMatrix(leash, 1, 0, 0, 1); + startTransaction.setCornerRadius(leash, 0); + startTransaction.setPosition(leash, bounds.left, bounds.top); + + if (mHasFadeOut && prevPipTaskChange.getTaskInfo().isVisible()) { + if (mPipAnimationController.getCurrentAnimator() != null) { + mPipAnimationController.getCurrentAnimator().cancel(); + } + startTransaction.setAlpha(leash, 1); + } mHasFadeOut = false; mCurrentPipTaskToken = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 63627938ec87..20c57fa5e566 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -38,6 +38,8 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -51,7 +53,6 @@ import java.util.List; */ public abstract class PipTransitionController implements Transitions.TransitionHandler { - protected final PipAnimationController mPipAnimationController; protected final PipBoundsAlgorithm mPipBoundsAlgorithm; protected final PipBoundsState mPipBoundsState; protected final ShellTaskOrganizer mShellTaskOrganizer; @@ -134,20 +135,18 @@ public abstract class PipTransitionController implements Transitions.TransitionH @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, - PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, - PipAnimationController pipAnimationController) { + PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm) { mPipBoundsState = pipBoundsState; mPipMenuController = pipMenuController; mShellTaskOrganizer = shellTaskOrganizer; mPipBoundsAlgorithm = pipBoundsAlgorithm; - mPipAnimationController = pipAnimationController; mTransitions = transitions; if (Transitions.ENABLE_SHELL_TRANSITIONS) { shellInit.addInitCallback(this::onInit, this); } } - private void onInit() { + protected void onInit() { mTransitions.addHandler(this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java deleted file mode 100644 index 8b98790d3499..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java +++ /dev/null @@ -1,137 +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.pip; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.util.TypedValue.COMPLEX_UNIT_DIP; - -import android.annotation.Nullable; -import android.app.ActivityTaskManager; -import android.app.ActivityTaskManager.RootTaskInfo; -import android.app.RemoteAction; -import android.content.ComponentName; -import android.content.Context; -import android.os.RemoteException; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.Pair; -import android.util.TypedValue; -import android.window.TaskSnapshot; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.protolog.ShellProtoLogGroup; - -import java.util.List; -import java.util.Objects; - -/** A class that includes convenience methods. */ -public class PipUtils { - private static final String TAG = "PipUtils"; - - // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. - private static final double EPSILON = 1e-7; - - /** - * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. - * The component name may be null if no such activity exists. - */ - public static Pair<ComponentName, Integer> getTopPipActivity(Context context) { - try { - final String sysUiPackageName = context.getPackageName(); - final RootTaskInfo pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); - if (pinnedTaskInfo != null && pinnedTaskInfo.childTaskIds != null - && pinnedTaskInfo.childTaskIds.length > 0) { - for (int i = pinnedTaskInfo.childTaskNames.length - 1; i >= 0; i--) { - ComponentName cn = ComponentName.unflattenFromString( - pinnedTaskInfo.childTaskNames[i]); - if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) { - return new Pair<>(cn, pinnedTaskInfo.childTaskUserIds[i]); - } - } - } - } catch (RemoteException e) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Unable to get pinned stack.", TAG); - } - return new Pair<>(null, 0); - } - - /** - * @return the pixels for a given dp value. - */ - public static int dpToPx(float dpValue, DisplayMetrics dm) { - return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm); - } - - /** - * @return true if the aspect ratios differ - */ - public static boolean aspectRatioChanged(float aspectRatio1, float aspectRatio2) { - return Math.abs(aspectRatio1 - aspectRatio2) > EPSILON; - } - - /** - * Checks whether title, description and intent match. - * Comparing icons would be good, but using equals causes false negatives - */ - public static boolean remoteActionsMatch(RemoteAction action1, RemoteAction action2) { - if (action1 == action2) return true; - if (action1 == null || action2 == null) return false; - return action1.isEnabled() == action2.isEnabled() - && action1.shouldShowIcon() == action2.shouldShowIcon() - && Objects.equals(action1.getTitle(), action2.getTitle()) - && Objects.equals(action1.getContentDescription(), action2.getContentDescription()) - && Objects.equals(action1.getActionIntent(), action2.getActionIntent()); - } - - /** - * Returns true if the actions in the lists match each other according to {@link - * PipUtils#remoteActionsMatch(RemoteAction, RemoteAction)}, including their position. - */ - public static boolean remoteActionsChanged(List<RemoteAction> list1, List<RemoteAction> list2) { - if (list1 == null && list2 == null) { - return false; - } - if (list1 == null || list2 == null) { - return true; - } - if (list1.size() != list2.size()) { - return true; - } - for (int i = 0; i < list1.size(); i++) { - if (!remoteActionsMatch(list1.get(i), list2.get(i))) { - return true; - } - } - return false; - } - - /** @return {@link TaskSnapshot} for a given task id. */ - @Nullable - public static TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) { - if (taskId <= 0) return null; - try { - return ActivityTaskManager.getService().getTaskSnapshot( - taskId, isLowResolution, false /* takeSnapshotIfNeeded */); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get task snapshot, taskId=" + taskId, e); - return null; - } - } -} 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 5e1b6becfa45..760652625f9e 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 @@ -38,12 +38,12 @@ import android.view.WindowManagerGlobal; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipMediaController; -import com.android.wm.shell.pip.PipMediaController.ActionListener; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipMediaController.ActionListener; +import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; -import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; 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 4a06d84ce90d..118ad9c4bfe3 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 @@ -15,7 +15,7 @@ */ package com.android.wm.shell.pip.phone; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; import android.annotation.NonNull; import android.content.Context; @@ -36,8 +36,8 @@ import androidx.annotation.BinderThread; import com.android.wm.shell.R; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import java.util.ArrayList; @@ -383,6 +383,9 @@ public class PipAccessibilityInteractionConnection { } @Override - public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {} + public void attachAccessibilityOverlayToWindow( + SurfaceControl sc, + int interactionId, + IAccessibilityInteractionConnectionCallback callback) {} } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 65727b6145e4..106486714a5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -75,6 +75,14 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.IPip; @@ -82,18 +90,10 @@ import com.android.wm.shell.pip.IPipAnimationListener; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipAppOpsListener; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.KeyguardChangeListener; @@ -123,14 +123,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb private static final long PIP_KEEP_CLEAR_AREAS_DELAY = SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200); - private boolean mEnablePipKeepClearAlgorithm = - SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true); - - @VisibleForTesting - void setEnablePipKeepClearAlgorithm(boolean value) { - mEnablePipKeepClearAlgorithm = value; - } - private Context mContext; protected ShellExecutor mMainExecutor; private DisplayController mDisplayController; @@ -142,7 +134,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb private PipBoundsAlgorithm mPipBoundsAlgorithm; private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm; private PipBoundsState mPipBoundsState; - private PipSizeSpecHandler mPipSizeSpecHandler; private PipDisplayLayoutState mPipDisplayLayoutState; private PipMotionHelper mPipMotionHelper; private PipTouchHandler mTouchHandler; @@ -167,10 +158,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb // early bail out if the change was caused by keyguard showing up return; } - if (!mEnablePipKeepClearAlgorithm) { - // early bail out if the keep clear areas feature is disabled - return; - } if (mPipBoundsState.isStashed()) { // don't move when stashed return; @@ -188,10 +175,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb } private void updatePipPositionForKeepClearAreas() { - if (!mEnablePipKeepClearAlgorithm) { - // early bail out if the keep clear areas feature is disabled - return; - } if (mIsKeyguardShowingOrAnimating) { // early bail out if the change was caused by keyguard showing up return; @@ -344,15 +327,17 @@ public class PipController implements PipTransitionController.PipTransitionCallb public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted, Set<Rect> unrestricted) { if (mPipDisplayLayoutState.getDisplayId() == displayId) { - if (mEnablePipKeepClearAlgorithm) { - mPipBoundsState.setKeepClearAreas(restricted, unrestricted); - - mMainExecutor.removeCallbacks( - mMovePipInResponseToKeepClearAreasChangeCallback); - mMainExecutor.executeDelayed( - mMovePipInResponseToKeepClearAreasChangeCallback, - PIP_KEEP_CLEAR_AREAS_DELAY); - } + mPipBoundsState.setKeepClearAreas(restricted, unrestricted); + + mMainExecutor.removeCallbacks( + mMovePipInResponseToKeepClearAreasChangeCallback); + mMainExecutor.executeDelayed( + mMovePipInResponseToKeepClearAreasChangeCallback, + PIP_KEEP_CLEAR_AREAS_DELAY); + + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "onKeepClearAreasChanged: restricted=%s, unrestricted=%s", + restricted, unrestricted); } } }; @@ -402,7 +387,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipBoundsAlgorithm pipBoundsAlgorithm, PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, PipBoundsState pipBoundsState, - PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, @@ -426,7 +410,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb return new PipController(context, shellInit, shellCommandHandler, shellController, displayController, pipAnimationController, pipAppOpsListener, - pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, + pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, @@ -444,7 +428,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipBoundsAlgorithm pipBoundsAlgorithm, PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, @NonNull PipBoundsState pipBoundsState, - PipSizeSpecHandler pipSizeSpecHandler, @NonNull PipDisplayLayoutState pipDisplayLayoutState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, @@ -461,8 +444,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb Optional<OneHandedController> oneHandedController, ShellExecutor mainExecutor ) { - - mContext = context; mShellCommandHandler = shellCommandHandler; mShellController = shellController; @@ -472,7 +453,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipKeepClearAlgorithm = pipKeepClearAlgorithm; mPipBoundsState = pipBoundsState; - mPipSizeSpecHandler = pipSizeSpecHandler; mPipDisplayLayoutState = pipDisplayLayoutState; mPipMotionHelper = pipMotionHelper; mPipTaskOrganizer = pipTaskOrganizer; @@ -493,7 +473,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb mDisplayInsetsController = displayInsetsController; mTabletopModeController = tabletopModeController; - shellInit.addInitCallback(this::onInit, this); + if (!PipUtils.isPip2ExperimentEnabled()) { + shellInit.addInitCallback(this::onInit, this); + } } private void onInit() { @@ -660,25 +642,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb // there's a keyguard present return; } - int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom; onDisplayChangedUncheck(mDisplayController .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()), false /* saveRestoreSnapFraction */); - int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom; - if (!mEnablePipKeepClearAlgorithm) { - // offset PiP to adjust for bottom inset change - int pipTop = mPipBoundsState.getBounds().top; - int diff = newMaxMovementBound - oldMaxMovementBound; - if (diff < 0 && pipTop > newMaxMovementBound) { - // bottom inset has increased, move PiP up if it is too low - mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), - newMaxMovementBound - pipTop); - } - if (diff > 0 && oldMaxMovementBound == pipTop) { - // bottom inset has decreased, move PiP down if it was by the edge - mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff); - } - } } }); @@ -707,7 +673,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Try to move the PiP window if we have entered PiP mode. if (mPipTransitionState.hasEnteredPip()) { final Rect pipBounds = mPipBoundsState.getBounds(); - final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets(); + final Point edgeInsets = mPipDisplayLayoutState.getScreenEdgeInsets(); if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) { // PiP bounds is too big to fit either half, bail early. return; @@ -766,7 +732,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsAlgorithm.onConfigurationChanged(mContext); mTouchHandler.onConfigurationChanged(); mPipBoundsState.onConfigurationChanged(); - mPipSizeSpecHandler.onConfigurationChanged(); + mPipDisplayLayoutState.onConfigurationChanged(); } @Override @@ -795,6 +761,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb } private void onDisplayChangedUncheck(DisplayLayout layout, boolean saveRestoreSnapFraction) { + if (mPipTransitionState.getInSwipePipToHomeTransition()) { + // If orientation is changed when performing swipe-pip animation, DisplayLayout has + // been updated in startSwipePipToHome. So it is unnecessary to update again when + // receiving onDisplayConfigurationChanged. This also avoids TouchHandler.userResizeTo + // update surface position in different orientation by the intermediate state. The + // desired resize will be done by the end of transition. + return; + } Runnable updateDisplayLayout = () -> { final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS && mPipDisplayLayoutState.getDisplayLayout().rotation() != layout.rotation(); @@ -939,12 +913,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb * Sets both shelf visibility and its height. */ private void setShelfHeight(boolean visible, int height) { - if (!mIsKeyguardShowingOrAnimating) { - setShelfHeightLocked(visible, height); - } + // turn this into Launcher keep clear area registration instead + setLauncherKeepClearAreaHeight(visible, height); } private void setLauncherKeepClearAreaHeight(boolean visible, int height) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height); if (visible) { Rect rect = new Rect( 0, mPipBoundsState.getDisplayBounds().bottom - height, @@ -1000,15 +975,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, PictureInPictureParams pictureInPictureParams, int launcherRotation, Rect hotseatKeepClearArea) { - - if (mEnablePipKeepClearAlgorithm) { - // pre-emptively add the keep clear area for Hotseat, so that it is taken into account - // when calculating the entry destination bounds of PiP window - mPipBoundsState.getRestrictedKeepClearAreas().add(hotseatKeepClearArea); - } else { - int shelfHeight = hotseatKeepClearArea.height(); - setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight); - } + // preemptively add the keep clear area for Hotseat, so that it is taken into account + // when calculating the entry destination bounds of PiP window + mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, + hotseatKeepClearArea); onDisplayRotationChangedNotInPip(mContext, launcherRotation); final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo, pictureInPictureParams); @@ -1204,7 +1174,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipTaskOrganizer.dump(pw, innerPrefix); mPipBoundsState.dump(pw, innerPrefix); mPipInputConsumer.dump(pw, innerPrefix); - mPipSizeSpecHandler.dump(pw, innerPrefix); mPipDisplayLayoutState.dump(pw, innerPrefix); } @@ -1253,6 +1222,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipController.this.showPictureInPictureMenu(); }); } + + @Override + public PipTransitionController getPipTransitionController() { + return mPipTransitionController; + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index 9729a4007bac..4e75847b6bc0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -33,11 +33,12 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import com.android.wm.shell.R; -import com.android.wm.shell.bubbles.DismissView; -import com.android.wm.shell.common.DismissCircleView; +import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.bubbles.DismissCircleView; +import com.android.wm.shell.common.bubbles.DismissView; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; -import com.android.wm.shell.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUiEventLogger; import kotlin.Unit; @@ -106,6 +107,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen } mTargetViewContainer = new DismissView(mContext); + DismissViewUtils.setup(mTargetViewContainer); mTargetView = mTargetViewContainer.getCircle(); mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> { if (!windowInsets.equals(mWindowInsets)) { 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 d7d335b856be..1b1ebc39b558 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 @@ -20,7 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.graphics.Rect; -import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipBoundsState; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 779c539a2097..fc34772f2fce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -66,8 +66,8 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.pip.PipUiEventLogger; -import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index 43d3f36f1fe5..c708b86e88c5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -20,10 +20,10 @@ import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_B import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW; import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_DISMISS; import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE; @@ -33,7 +33,6 @@ import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; -import android.os.SystemProperties; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; @@ -41,26 +40,23 @@ import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.animation.PhysicsAnimator; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; -import com.android.wm.shell.pip.PipAppOpsListener; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import java.util.function.Consumer; - import kotlin.Unit; import kotlin.jvm.functions.Function0; +import java.util.function.Consumer; + /** * A helper to animate and manipulate the PiP. */ public class PipMotionHelper implements PipAppOpsListener.Callback, FloatingContentCoordinator.FloatingContent { - - public static final boolean ENABLE_FLING_TO_DISMISS_PIP = - SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", false); private static final String TAG = "PipMotionHelper"; private static final boolean DEBUG = false; @@ -707,7 +703,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, loc[1] = animatedPipBounds.top; } }; - mMagnetizedPip.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_PIP); + mMagnetizedPip.setFlingToTargetEnabled(false); } return mMagnetizedPip; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index abe2db094a5c..e5f9fdc7a740 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -46,11 +46,12 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.policy.TaskResizingAlgorithm; import com.android.wm.shell.R; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm; +import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipTaskOrganizer; -import com.android.wm.shell.pip.PipUiEventLogger; import java.io.PrintWriter; import java.util.function.Consumer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java deleted file mode 100644 index 7971c049ff3b..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java +++ /dev/null @@ -1,535 +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.pip.phone; - -import static com.android.wm.shell.pip.PipUtils.dpToPx; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.SystemProperties; -import android.util.Size; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.wm.shell.R; -import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.pip.PipDisplayLayoutState; - -import java.io.PrintWriter; - -/** - * Acts as a source of truth for appropriate size spec for PIP. - */ -public class PipSizeSpecHandler { - private static final String TAG = PipSizeSpecHandler.class.getSimpleName(); - - @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState; - - private final SizeSpecSource mSizeSpecSourceImpl; - - /** The preferred minimum (and default minimum) size specified by apps. */ - @Nullable private Size mOverrideMinSize; - private int mOverridableMinSize; - - /** Used to store values obtained from resource files. */ - private Point mScreenEdgeInsets; - private float mMinAspectRatioForMinSize; - private float mMaxAspectRatioForMinSize; - private int mDefaultMinSize; - - @NonNull private final Context mContext; - - private interface SizeSpecSource { - /** Returns max size allowed for the PIP window */ - Size getMaxSize(float aspectRatio); - - /** Returns default size for the PIP window */ - Size getDefaultSize(float aspectRatio); - - /** Returns min size allowed for the PIP window */ - Size getMinSize(float aspectRatio); - - /** Returns the adjusted size based on current size and target aspect ratio */ - Size getSizeForAspectRatio(Size size, float aspectRatio); - - /** Updates internal resources on configuration changes */ - default void reloadResources() {} - } - - /** - * Determines PIP window size optimized for large screens and aspect ratios close to 1:1 - */ - private class SizeSpecLargeScreenOptimizedImpl implements SizeSpecSource { - private static final float DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16; - - /** Default and minimum percentages for the PIP size logic. */ - private final float mDefaultSizePercent; - private final float mMinimumSizePercent; - - /** Aspect ratio that the PIP size spec logic optimizes for. */ - private float mOptimizedAspectRatio; - - private SizeSpecLargeScreenOptimizedImpl() { - mDefaultSizePercent = Float.parseFloat(SystemProperties - .get("com.android.wm.shell.pip.phone.def_percentage", "0.6")); - mMinimumSizePercent = Float.parseFloat(SystemProperties - .get("com.android.wm.shell.pip.phone.min_percentage", "0.5")); - } - - @Override - public void reloadResources() { - final Resources res = mContext.getResources(); - - mOptimizedAspectRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio); - // make sure the optimized aspect ratio is valid with a default value to fall back to - if (mOptimizedAspectRatio > 1) { - mOptimizedAspectRatio = DEFAULT_OPTIMIZED_ASPECT_RATIO; - } - } - - /** - * Calculates the max size of PIP. - * - * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge. - * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the - * whole screen. A linear function is used to calculate these sizes. - * - * @param aspectRatio aspect ratio of the PIP window - * @return dimensions of the max size of the PIP - */ - @Override - public Size getMaxSize(float aspectRatio) { - final int totalHorizontalPadding = getInsetBounds().left - + (getDisplayBounds().width() - getInsetBounds().right); - final int totalVerticalPadding = getInsetBounds().top - + (getDisplayBounds().height() - getInsetBounds().bottom); - - final int shorterLength = (int) (1f * Math.min( - getDisplayBounds().width() - totalHorizontalPadding, - getDisplayBounds().height() - totalVerticalPadding)); - - int maxWidth, maxHeight; - - // use the optimized max sizing logic only within a certain aspect ratio range - if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) { - // this formula and its derivation is explained in b/198643358#comment16 - maxWidth = (int) (mOptimizedAspectRatio * shorterLength - + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 - + aspectRatio)); - maxHeight = Math.round(maxWidth / aspectRatio); - } else { - if (aspectRatio > 1f) { - maxWidth = shorterLength; - maxHeight = Math.round(maxWidth / aspectRatio); - } else { - maxHeight = shorterLength; - maxWidth = Math.round(maxHeight * aspectRatio); - } - } - - return new Size(maxWidth, maxHeight); - } - - /** - * Decreases the dimensions by a percentage relative to max size to get default size. - * - * @param aspectRatio aspect ratio of the PIP window - * @return dimensions of the default size of the PIP - */ - @Override - public Size getDefaultSize(float aspectRatio) { - Size minSize = this.getMinSize(aspectRatio); - - if (mOverrideMinSize != null) { - return minSize; - } - - Size maxSize = this.getMaxSize(aspectRatio); - - int defaultWidth = Math.max(Math.round(maxSize.getWidth() * mDefaultSizePercent), - minSize.getWidth()); - int defaultHeight = Math.round(defaultWidth / aspectRatio); - - return new Size(defaultWidth, defaultHeight); - } - - /** - * Decreases the dimensions by a certain percentage relative to max size to get min size. - * - * @param aspectRatio aspect ratio of the PIP window - * @return dimensions of the min size of the PIP - */ - @Override - public Size getMinSize(float aspectRatio) { - // if there is an overridden min size provided, return that - if (mOverrideMinSize != null) { - return adjustOverrideMinSizeToAspectRatio(aspectRatio); - } - - Size maxSize = this.getMaxSize(aspectRatio); - - int minWidth = Math.round(maxSize.getWidth() * mMinimumSizePercent); - int minHeight = Math.round(maxSize.getHeight() * mMinimumSizePercent); - - // make sure the calculated min size is not smaller than the allowed default min size - if (aspectRatio > 1f) { - minHeight = Math.max(minHeight, mDefaultMinSize); - minWidth = Math.round(minHeight * aspectRatio); - } else { - minWidth = Math.max(minWidth, mDefaultMinSize); - minHeight = Math.round(minWidth / aspectRatio); - } - return new Size(minWidth, minHeight); - } - - /** - * Returns the size for target aspect ratio making sure new size conforms with the rules. - * - * <p>Recalculates the dimensions such that the target aspect ratio is achieved, while - * maintaining the same maximum size to current size ratio. - * - * @param size current size - * @param aspectRatio target aspect ratio - */ - @Override - public Size getSizeForAspectRatio(Size size, float aspectRatio) { - float currAspectRatio = (float) size.getWidth() / size.getHeight(); - - // getting the percentage of the max size that current size takes - Size currentMaxSize = getMaxSize(currAspectRatio); - float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth(); - - // getting the max size for the target aspect ratio - Size updatedMaxSize = getMaxSize(aspectRatio); - - int width = Math.round(updatedMaxSize.getWidth() * currentPercent); - int height = Math.round(updatedMaxSize.getHeight() * currentPercent); - - // adjust the dimensions if below allowed min edge size - if (width < getMinEdgeSize() && aspectRatio <= 1) { - width = getMinEdgeSize(); - height = Math.round(width / aspectRatio); - } else if (height < getMinEdgeSize() && aspectRatio > 1) { - height = getMinEdgeSize(); - width = Math.round(height * aspectRatio); - } - - // reduce the dimensions of the updated size to the calculated percentage - return new Size(width, height); - } - } - - private class SizeSpecDefaultImpl implements SizeSpecSource { - private float mDefaultSizePercent; - private float mMinimumSizePercent; - - @Override - public void reloadResources() { - final Resources res = mContext.getResources(); - - mMaxAspectRatioForMinSize = res.getFloat( - R.dimen.config_pictureInPictureAspectRatioLimitForMinSize); - mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize; - - mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent); - mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1); - } - - @Override - public Size getMaxSize(float aspectRatio) { - final int shorterLength = Math.min(getDisplayBounds().width(), - getDisplayBounds().height()); - - final int totalHorizontalPadding = getInsetBounds().left - + (getDisplayBounds().width() - getInsetBounds().right); - final int totalVerticalPadding = getInsetBounds().top - + (getDisplayBounds().height() - getInsetBounds().bottom); - - final int maxWidth, maxHeight; - - if (aspectRatio > 1f) { - maxWidth = (int) Math.max(getDefaultSize(aspectRatio).getWidth(), - shorterLength - totalHorizontalPadding); - maxHeight = (int) (maxWidth / aspectRatio); - } else { - maxHeight = (int) Math.max(getDefaultSize(aspectRatio).getHeight(), - shorterLength - totalVerticalPadding); - maxWidth = (int) (maxHeight * aspectRatio); - } - - return new Size(maxWidth, maxHeight); - } - - @Override - public Size getDefaultSize(float aspectRatio) { - if (mOverrideMinSize != null) { - return this.getMinSize(aspectRatio); - } - - final int smallestDisplaySize = Math.min(getDisplayBounds().width(), - getDisplayBounds().height()); - final int minSize = (int) Math.max(getMinEdgeSize(), - smallestDisplaySize * mDefaultSizePercent); - - final int width; - final int height; - - if (aspectRatio <= mMinAspectRatioForMinSize - || aspectRatio > mMaxAspectRatioForMinSize) { - // Beyond these points, we can just use the min size as the shorter edge - if (aspectRatio <= 1) { - // Portrait, width is the minimum size - width = minSize; - height = Math.round(width / aspectRatio); - } else { - // Landscape, height is the minimum size - height = minSize; - width = Math.round(height * aspectRatio); - } - } else { - // Within these points, ensure that the bounds fit within the radius of the limits - // at the points - final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize; - final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize); - height = (int) Math.round(Math.sqrt((radius * radius) - / (aspectRatio * aspectRatio + 1))); - width = Math.round(height * aspectRatio); - } - - return new Size(width, height); - } - - @Override - public Size getMinSize(float aspectRatio) { - if (mOverrideMinSize != null) { - return adjustOverrideMinSizeToAspectRatio(aspectRatio); - } - - final int shorterLength = Math.min(getDisplayBounds().width(), - getDisplayBounds().height()); - final int minWidth, minHeight; - - if (aspectRatio > 1f) { - minWidth = (int) Math.min(getDefaultSize(aspectRatio).getWidth(), - shorterLength * mMinimumSizePercent); - minHeight = (int) (minWidth / aspectRatio); - } else { - minHeight = (int) Math.min(getDefaultSize(aspectRatio).getHeight(), - shorterLength * mMinimumSizePercent); - minWidth = (int) (minHeight * aspectRatio); - } - - return new Size(minWidth, minHeight); - } - - @Override - public Size getSizeForAspectRatio(Size size, float aspectRatio) { - final int smallestSize = Math.min(size.getWidth(), size.getHeight()); - final int minSize = Math.max(getMinEdgeSize(), smallestSize); - - final int width; - final int height; - if (aspectRatio <= 1) { - // Portrait, width is the minimum size. - width = minSize; - height = Math.round(width / aspectRatio); - } else { - // Landscape, height is the minimum size - height = minSize; - width = Math.round(height * aspectRatio); - } - - return new Size(width, height); - } - } - - public PipSizeSpecHandler(Context context, PipDisplayLayoutState pipDisplayLayoutState) { - mContext = context; - mPipDisplayLayoutState = pipDisplayLayoutState; - - // choose between two implementations of size spec logic - if (supportsPipSizeLargeScreen()) { - mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl(); - } else { - mSizeSpecSourceImpl = new SizeSpecDefaultImpl(); - } - - reloadResources(); - } - - /** Reloads the resources */ - public void onConfigurationChanged() { - reloadResources(); - } - - private void reloadResources() { - final Resources res = mContext.getResources(); - - mDefaultMinSize = res.getDimensionPixelSize( - R.dimen.default_minimal_size_pip_resizable_task); - mOverridableMinSize = res.getDimensionPixelSize( - R.dimen.overridable_minimal_size_pip_resizable_task); - - final String screenEdgeInsetsDpString = res.getString( - R.string.config_defaultPictureInPictureScreenEdgeInsets); - final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() - ? Size.parseSize(screenEdgeInsetsDpString) - : null; - mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point() - : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()), - dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics())); - - // update the internal resources of the size spec source's stub - mSizeSpecSourceImpl.reloadResources(); - } - - @NonNull - private Rect getDisplayBounds() { - return mPipDisplayLayoutState.getDisplayBounds(); - } - - public Point getScreenEdgeInsets() { - return mScreenEdgeInsets; - } - - /** - * Returns the inset bounds the PIP window can be visible in. - */ - public Rect getInsetBounds() { - Rect insetBounds = new Rect(); - DisplayLayout displayLayout = mPipDisplayLayoutState.getDisplayLayout(); - Rect insets = displayLayout.stableInsets(); - insetBounds.set(insets.left + mScreenEdgeInsets.x, - insets.top + mScreenEdgeInsets.y, - displayLayout.width() - insets.right - mScreenEdgeInsets.x, - displayLayout.height() - insets.bottom - mScreenEdgeInsets.y); - return insetBounds; - } - - /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ - public void setOverrideMinSize(@Nullable Size overrideMinSize) { - mOverrideMinSize = overrideMinSize; - } - - /** Returns the preferred minimal size specified by the activity in PIP. */ - @Nullable - public Size getOverrideMinSize() { - if (mOverrideMinSize != null - && (mOverrideMinSize.getWidth() < mOverridableMinSize - || mOverrideMinSize.getHeight() < mOverridableMinSize)) { - return new Size(mOverridableMinSize, mOverridableMinSize); - } - - return mOverrideMinSize; - } - - /** Returns the minimum edge size of the override minimum size, or 0 if not set. */ - public int getOverrideMinEdgeSize() { - if (mOverrideMinSize == null) return 0; - return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight()); - } - - public int getMinEdgeSize() { - return mOverrideMinSize == null ? mDefaultMinSize : getOverrideMinEdgeSize(); - } - - /** - * Returns the size for the max size spec. - */ - public Size getMaxSize(float aspectRatio) { - return mSizeSpecSourceImpl.getMaxSize(aspectRatio); - } - - /** - * Returns the size for the default size spec. - */ - public Size getDefaultSize(float aspectRatio) { - return mSizeSpecSourceImpl.getDefaultSize(aspectRatio); - } - - /** - * Returns the size for the min size spec. - */ - public Size getMinSize(float aspectRatio) { - return mSizeSpecSourceImpl.getMinSize(aspectRatio); - } - - /** - * Returns the adjusted size so that it conforms to the given aspectRatio. - * - * @param size current size - * @param aspectRatio target aspect ratio - */ - public Size getSizeForAspectRatio(@NonNull Size size, float aspectRatio) { - if (size.equals(mOverrideMinSize)) { - return adjustOverrideMinSizeToAspectRatio(aspectRatio); - } - - return mSizeSpecSourceImpl.getSizeForAspectRatio(size, aspectRatio); - } - - /** - * Returns the adjusted overridden min size if it is set; otherwise, returns null. - * - * <p>Overridden min size needs to be adjusted in its own way while making sure that the target - * aspect ratio is maintained - * - * @param aspectRatio target aspect ratio - */ - @Nullable - @VisibleForTesting - Size adjustOverrideMinSizeToAspectRatio(float aspectRatio) { - if (mOverrideMinSize == null) { - return null; - } - final Size size = getOverrideMinSize(); - final float sizeAspectRatio = size.getWidth() / (float) size.getHeight(); - if (sizeAspectRatio > aspectRatio) { - // Size is wider, fix the width and increase the height - return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio)); - } else { - // Size is taller, fix the height and adjust the width. - return new Size((int) (size.getHeight() * aspectRatio), size.getHeight()); - } - } - - @VisibleForTesting - boolean supportsPipSizeLargeScreen() { - // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource - // can be injected - return SystemProperties - .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv(); - } - - private boolean isTv() { - return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); - } - - /** Dumps internal state. */ - public void dump(PrintWriter pw, String prefix) { - final String innerPrefix = prefix + " "; - pw.println(prefix + TAG); - pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl); - pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize); - pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 2c4f76b1f34b..2ce4fb9e297b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -18,10 +18,10 @@ package com.android.wm.shell.pip.phone; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL; import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE; import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE; @@ -34,7 +34,6 @@ import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; -import android.os.SystemProperties; import android.provider.DeviceConfig; import android.util.Size; import android.view.DisplayCutout; @@ -51,12 +50,14 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellInit; @@ -71,20 +72,12 @@ public class PipTouchHandler { private static final String TAG = "PipTouchHandler"; private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f; - private boolean mEnablePipKeepClearAlgorithm = - SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true); - - @VisibleForTesting - void setEnablePipKeepClearAlgorithm(boolean value) { - mEnablePipKeepClearAlgorithm = value; - } - // Allow PIP to resize to a slightly bigger state upon touch private boolean mEnableResize; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; @NonNull private final PipBoundsState mPipBoundsState; - @NonNull private final PipSizeSpecHandler mPipSizeSpecHandler; + @NonNull private final SizeSpecSource mSizeSpecSource; private final PipUiEventLogger mPipUiEventLogger; private final PipDismissTargetHandler mPipDismissTargetHandler; private final PipTaskOrganizer mPipTaskOrganizer; @@ -178,7 +171,7 @@ public class PipTouchHandler { PhonePipMenuController menuController, PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, - @NonNull PipSizeSpecHandler pipSizeSpecHandler, + @NonNull SizeSpecSource sizeSpecSource, PipTaskOrganizer pipTaskOrganizer, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, @@ -189,7 +182,7 @@ public class PipTouchHandler { mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; - mPipSizeSpecHandler = pipSizeSpecHandler; + mSizeSpecSource = sizeSpecSource; mPipTaskOrganizer = pipTaskOrganizer; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; @@ -228,7 +221,9 @@ public class PipTouchHandler { // TODO(b/181599115): This should really be initializes as part of the pip controller, but // until all PIP implementations derive from the controller, just initialize the touch handler // if it is needed - shellInit.addInitCallback(this::onInit, this); + if (!PipUtils.isPip2ExperimentEnabled()) { + shellInit.addInitCallback(this::onInit, this); + } } /** @@ -410,7 +405,7 @@ public class PipTouchHandler { // Calculate the expanded size float aspectRatio = (float) normalBounds.width() / normalBounds.height(); - Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio); + Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio); mPipBoundsState.setExpandedBounds( new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight())); Rect expandedMovementBounds = new Rect(); @@ -426,48 +421,6 @@ public class PipTouchHandler { mIsImeShowing ? mImeOffset : 0, !mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0); - // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not - // occluded by the IME or shelf. - if (fromImeAdjustment || fromShelfAdjustment) { - if (mTouchState.isUserInteracting() && mTouchState.isDragging()) { - // Defer the update of the current movement bounds until after the user finishes - // touching the screen - } else if (mEnablePipKeepClearAlgorithm) { - // Ignore moving PiP if keep clear algorithm is enabled, since IME and shelf height - // now are accounted for in the keep clear algorithm calculations - } else { - final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu(); - final Rect toMovementBounds = new Rect(); - mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds, - toMovementBounds, mIsImeShowing ? mImeHeight : 0); - final int prevBottom = mPipBoundsState.getMovementBounds().bottom - - mMovementBoundsExtraOffsets; - // This is to handle landscape fullscreen IMEs, don't apply the extra offset in this - // case - final int toBottom = toMovementBounds.bottom < toMovementBounds.top - ? toMovementBounds.bottom - : toMovementBounds.bottom - extraOffset; - - if (isExpanded) { - curBounds.set(mPipBoundsState.getExpandedBounds()); - mPipBoundsAlgorithm.getSnapAlgorithm().applySnapFraction(curBounds, - toMovementBounds, mSavedSnapFraction); - } - - if (prevBottom < toBottom) { - // The movement bounds are expanding - if (curBounds.top > prevBottom - mBottomOffsetBufferPx) { - mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top); - } - } else if (prevBottom > toBottom) { - // The movement bounds are shrinking - if (curBounds.top > toBottom - mBottomOffsetBufferPx) { - mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top); - } - } - } - } - // Update the movement bounds after doing the calculations based on the old movement bounds // above mPipBoundsState.setNormalMovementBounds(normalMovementBounds); @@ -514,10 +467,10 @@ public class PipTouchHandler { private void updatePinchResizeSizeConstraints(float aspectRatio) { final int minWidth, minHeight, maxWidth, maxHeight; - minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth(); - minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight(); - maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth(); - maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight(); + minWidth = mSizeSpecSource.getMinSize(aspectRatio).getWidth(); + minHeight = mSizeSpecSource.getMinSize(aspectRatio).getHeight(); + maxWidth = mSizeSpecSource.getMaxSize(aspectRatio).getWidth(); + maxHeight = mSizeSpecSource.getMaxSize(aspectRatio).getHeight(); mPipResizeGestureHandler.updateMinSize(minWidth, minHeight); mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight); @@ -591,6 +544,13 @@ public class PipTouchHandler { return true; } + // Ignore the motion event When the entry animation is waiting to be started + if (!mTouchState.isUserInteracting() && mPipTaskOrganizer.isEntryScheduled()) { + ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Waiting to start the entry animation, skip the motion event.", TAG); + return true; + } + // Update the touch state mTouchState.onTouchEvent(ev); 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 index 5f6b3fe1e250..fc0b876e1bde 100644 --- 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 @@ -77,6 +77,19 @@ abstract class TvPipAction { return mActionType; } + static String getActionTypeString(@ActionType int actionType) { + switch (actionType) { + case ACTION_FULLSCREEN: return "ACTION_FULLSCREEN"; + case ACTION_CLOSE: return "ACTION_CLOSE"; + case ACTION_MOVE: return "ACTION_MOVE"; + case ACTION_EXPAND_COLLAPSE: return "ACTION_EXPAND_COLLAPSE"; + case ACTION_CUSTOM: return "ACTION_CUSTOM"; + case ACTION_CUSTOM_CLOSE: return "ACTION_CUSTOM_CLOSE"; + default: + return "UNDEFINED"; + } + } + abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler); abstract PendingIntent getPendingIntent(); 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 index 3b44f10ebe62..6b890c49b713 100644 --- 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 @@ -35,8 +35,8 @@ 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.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.ArrayList; @@ -56,8 +56,10 @@ public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler { private final List<Listener> mListeners = new ArrayList<>(); private final TvPipAction.SystemActionsHandler mSystemActionsHandler; - private final List<TvPipAction> mActionsList; + private final List<TvPipAction> mActionsList = new ArrayList<>(); + private final TvPipSystemAction mFullscreenAction; private final TvPipSystemAction mDefaultCloseAction; + private final TvPipSystemAction mMoveAction; private final TvPipSystemAction mExpandCollapseAction; private final List<RemoteAction> mMediaActions = new ArrayList<>(); @@ -67,26 +69,27 @@ public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler { TvPipAction.SystemActionsHandler systemActionsHandler) { mSystemActionsHandler = systemActionsHandler; - mActionsList = new ArrayList<>(); - mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen, + mFullscreenAction = new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen, R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context, - mSystemActionsHandler)); - + 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)); - + mMoveAction = 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); + initActions(); pipMediaController.addActionListener(this::onMediaActionsChanged); } + private void initActions() { + mActionsList.add(mFullscreenAction); + mActionsList.add(mDefaultCloseAction); + mActionsList.add(mMoveAction); + } + @Override public void executeAction(@TvPipAction.ActionType int actionType) { if (mSystemActionsHandler != null) { @@ -199,6 +202,14 @@ public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler { } } + void reset() { + mActionsList.clear(); + mMediaActions.clear(); + mAppActions.clear(); + + initActions(); + } + List<TvPipAction> getActionsList() { return mActionsList; } 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 825b96921a22..a48e969fde35 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 @@ -36,10 +36,11 @@ import androidx.annotation.NonNull; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; -import com.android.wm.shell.pip.PipSnapAlgorithm; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -62,9 +63,10 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { public TvPipBoundsAlgorithm(Context context, @NonNull TvPipBoundsState tvPipBoundsState, @NonNull PipSnapAlgorithm pipSnapAlgorithm, - @NonNull PipSizeSpecHandler pipSizeSpecHandler) { + @NonNull PipDisplayLayoutState pipDisplayLayoutState, + @NonNull SizeSpecSource sizeSpecSource) { super(context, tvPipBoundsState, pipSnapAlgorithm, - new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler); + new PipKeepClearAlgorithmInterface() {}, pipDisplayLayoutState, sizeSpecSource); this.mTvPipBoundsState = tvPipBoundsState; this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(); reloadResources(context); @@ -291,7 +293,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { expandedSize = mTvPipBoundsState.getTvExpandedSize(); } else { int maxHeight = displayLayout.height() - - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y) + - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().y) - pipDecorations.top - pipDecorations.bottom; float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio; @@ -311,7 +313,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { expandedSize = mTvPipBoundsState.getTvExpandedSize(); } else { int maxWidth = displayLayout.width() - - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x) + - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().x) - pipDecorations.left - pipDecorations.right; float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio; if (maxWidth > aspectRatioWidth) { 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 8d4a38442ce5..8a215b4b2e25 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 @@ -16,7 +16,7 @@ package com.android.wm.shell.pip.tv; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; import android.annotation.NonNull; import android.annotation.Nullable; 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 e1737eccc6e1..2b3a93e3c3e8 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 @@ -29,10 +29,10 @@ 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; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.SizeSpecSource; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -76,11 +76,12 @@ public class TvPipBoundsState extends PipBoundsState { private Insets mPipMenuTemporaryDecorInsets = Insets.NONE; public TvPipBoundsState(@NonNull Context context, - @NonNull PipSizeSpecHandler pipSizeSpecHandler, + @NonNull SizeSpecSource sizeSpecSource, @NonNull PipDisplayLayoutState pipDisplayLayoutState) { - super(context, pipSizeSpecHandler, pipDisplayLayoutState); + super(context, sizeSpecSource, pipDisplayLayoutState); mContext = context; updateDefaultGravity(); + mTvPipGravity = mDefaultGravity; mPreviousCollapsedGravity = mDefaultGravity; mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE); 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 02eeb2ac4fd5..72115fdefa05 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 @@ -47,12 +47,12 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipAppOpsListener; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; @@ -478,6 +478,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mActionBroadcastReceiver.unregister(); mTvPipMenuController.closeMenu(); + mTvPipActionsProvider.reset(); mTvPipBoundsState.resetTvPipState(); mTvPipBoundsController.reset(); setState(STATE_NO_PIP); 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 a94bd6ec1040..93f6826ac96b 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 @@ -21,12 +21,12 @@ import android.graphics.Point import android.graphics.Rect import android.util.Size import android.view.Gravity -import com.android.wm.shell.pip.PipBoundsState -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM -import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT -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.common.pip.PipBoundsState +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -54,11 +54,11 @@ class TvPipKeepClearAlgorithm() { * the unstash timeout if already stashed. */ data class Placement( - val bounds: Rect, - val anchorBounds: Rect, - @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE, - val unstashDestinationBounds: Rect? = null, - val triggerStash: Boolean = false + val bounds: Rect, + val anchorBounds: Rect, + @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE, + val unstashDestinationBounds: Rect? = null, + val triggerStash: Boolean = false ) { /** Bounds to use if the PiP should not be stashed. */ fun getUnstashedBounds() = unstashDestinationBounds ?: bounds 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 b2a189b45d6c..ee55211a73a9 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 @@ -62,13 +62,16 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis private SurfaceControl mLeash; private TvPipMenuView mPipMenuView; private TvPipBackgroundView mPipBackgroundView; - private boolean mMenuIsFocused; @TvPipMenuMode private int mCurrentMenuMode = MODE_NO_MENU; @TvPipMenuMode private int mPrevMenuMode = MODE_NO_MENU; + /** When the window gains focus, enter this menu mode */ + @TvPipMenuMode + private int mMenuModeOnFocus = MODE_ALL_ACTIONS_MENU; + @IntDef(prefix = { "MODE_" }, value = { MODE_NO_MENU, MODE_MOVE_MENU, @@ -170,6 +173,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis mPipMenuView = createTvPipMenuView(); setUpViewSurfaceZOrder(mPipMenuView, 1); addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE); + mPipMenuView.getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> { + onPipWindowFocusChanged(hasFocus); + }); } @VisibleForTesting @@ -224,13 +230,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis void showMovementMenu() { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMovementMenu()", TAG); - switchToMenuMode(MODE_MOVE_MENU); + requestMenuMode(MODE_MOVE_MENU); } @Override public void showMenu() { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG); - switchToMenuMode(MODE_ALL_ACTIONS_MENU, true); + mPipMenuView.resetMenu(); + requestMenuMode(MODE_ALL_ACTIONS_MENU); } void onPipTransitionToTargetBoundsStarted(Rect targetBounds) { @@ -250,7 +257,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis void closeMenu() { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: closeMenu()", TAG); - switchToMenuMode(MODE_NO_MENU); + requestMenuMode(MODE_NO_MENU); } @Override @@ -392,11 +399,15 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } } - // Start methods handling {@link TvPipMenuMode} + // Beginning of convenience methods for {@link TvPipMenuMode} @VisibleForTesting boolean isMenuOpen() { - return mCurrentMenuMode != MODE_NO_MENU; + return isMenuOpen(mCurrentMenuMode); + } + + private static boolean isMenuOpen(@TvPipMenuMode int menuMode) { + return menuMode != MODE_NO_MENU; } @VisibleForTesting @@ -409,31 +420,93 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis return mCurrentMenuMode == MODE_ALL_ACTIONS_MENU; } - private void switchToMenuMode(@TvPipMenuMode int menuMode) { - switchToMenuMode(menuMode, false); + @VisibleForTesting + String getMenuModeString() { + return getMenuModeString(mCurrentMenuMode); } - private void switchToMenuMode(@TvPipMenuMode int menuMode, boolean resetMenu) { - // Note: we intentionally don't return early here, because the TvPipMenuView needs to - // refresh the Ui even if there is no menu mode change. - mPrevMenuMode = mCurrentMenuMode; - mCurrentMenuMode = menuMode; + static String getMenuModeString(@TvPipMenuMode int menuMode) { + switch(menuMode) { + case MODE_NO_MENU: + return "MODE_NO_MENU"; + case MODE_MOVE_MENU: + return "MODE_MOVE_MENU"; + case MODE_ALL_ACTIONS_MENU: + return "MODE_ALL_ACTIONS_MENU"; + default: + return "Unknown"; + } + } + + // Beginning of methods handling switching between menu modes + + private void requestMenuMode(@TvPipMenuMode int menuMode) { + if (isMenuOpen() == isMenuOpen(menuMode)) { + // No need to request a focus change. We can directly switch to the new mode. + switchToMenuMode(menuMode); + } else { + if (isMenuOpen(menuMode)) { + mMenuModeOnFocus = menuMode; + } + + // Send a request to gain window focus if the menu is open, or lose window focus + // otherwise. Once the focus change happens, we will request the new mode in the + // callback {@link #onPipWindowFocusChanged}. + requestPipMenuFocus(isMenuOpen(menuMode)); + } + // Note: we don't handle cases where there is a focus change currently in flight, because + // this is very unlikely to happen in practice and would complicate the logic. + } + + private void requestPipMenuFocus(boolean focus) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: requestPipMenuFocus(%b)", TAG, focus); + + try { + WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, + mSystemWindows.getFocusGrantToken(mPipMenuView), focus); + } catch (Exception e) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Unable to update focus, %s", TAG, e); + } + } + + /** + * Called when the menu window gains or loses focus. + */ + @VisibleForTesting + void onPipWindowFocusChanged(boolean focused) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: onPipWindowFocusChanged - focused=%b", TAG, focused); + switchToMenuMode(focused ? mMenuModeOnFocus : MODE_NO_MENU); + + // Reset the default menu mode for focused state. + mMenuModeOnFocus = MODE_ALL_ACTIONS_MENU; + } + + /** + * Immediately switches to the menu mode in the given request. Updates the mDelegate and the UI. + * Doesn't handle any focus changes. + */ + private void switchToMenuMode(@TvPipMenuMode int menuMode) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: switchToMenuMode: from=%s, to=%s", TAG, getMenuModeString(), + getMenuModeString(menuMode)); - ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: switchToMenuMode: setting mCurrentMenuMode=%s, mPrevMenuMode=%s", TAG, - getMenuModeString(), getMenuModeString(mPrevMenuMode)); + if (mCurrentMenuMode == menuMode) return; - updateUiOnNewMenuModeRequest(resetMenu); + mPrevMenuMode = mCurrentMenuMode; + mCurrentMenuMode = menuMode; + updateUiOnNewMenuModeRequest(); updateDelegateOnNewMenuModeRequest(); } - private void updateUiOnNewMenuModeRequest(boolean resetMenu) { + private void updateUiOnNewMenuModeRequest() { if (mPipMenuView == null || mPipBackgroundView == null) return; mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity()); - mPipMenuView.transitionToMenuMode(mCurrentMenuMode, resetMenu); + mPipMenuView.transitionToMenuMode(mCurrentMenuMode); mPipBackgroundView.transitionToMenuMode(mCurrentMenuMode); - grantPipMenuFocus(mCurrentMenuMode != MODE_NO_MENU); } private void updateDelegateOnNewMenuModeRequest() { @@ -444,29 +517,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis mDelegate.onInMoveModeChanged(); } - if (mCurrentMenuMode == MODE_NO_MENU) { + if (!isMenuOpen()) { mDelegate.onMenuClosed(); } } - @VisibleForTesting - String getMenuModeString() { - return getMenuModeString(mCurrentMenuMode); - } - - static String getMenuModeString(@TvPipMenuMode int menuMode) { - switch(menuMode) { - case MODE_NO_MENU: - return "MODE_NO_MENU"; - case MODE_MOVE_MENU: - return "MODE_MOVE_MENU"; - case MODE_ALL_ACTIONS_MENU: - return "MODE_ALL_ACTIONS_MENU"; - default: - return "Unknown"; - } - } - // Start {@link TvPipMenuView.Delegate} methods @Override @@ -476,42 +531,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } @Override - public void onBackPress() { - if (!onExitMoveMode()) { - closeMenu(); - } - } - - @Override - public boolean onExitMoveMode() { + public void onExitCurrentMenuMode() { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onExitMoveMode - mCurrentMenuMode=%s", TAG, getMenuModeString()); - - final int saveMenuMode = mCurrentMenuMode; - if (isInMoveMode()) { - switchToMenuMode(mPrevMenuMode); - } - return saveMenuMode == MODE_MOVE_MENU; + "%s: onExitCurrentMenuMode - mCurrentMenuMode=%s", TAG, getMenuModeString()); + requestMenuMode(isInMoveMode() ? mPrevMenuMode : MODE_NO_MENU); } @Override - public boolean onPipMovement(int keycode) { + public void onPipMovement(int keycode) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onPipMovement - mCurrentMenuMode=%s", TAG, getMenuModeString()); if (isInMoveMode()) { mDelegate.movePip(keycode); } - return isInMoveMode(); - } - - @Override - public void onPipWindowFocusChanged(boolean focused) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onPipWindowFocusChanged - focused=%b", TAG, focused); - mMenuIsFocused = focused; - if (!focused && isMenuOpen()) { - closeMenu(); - } } interface Delegate { @@ -524,21 +556,6 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis void closeEduText(); } - private void grantPipMenuFocus(boolean grantFocus) { - if (mMenuIsFocused == grantFocus) return; - - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: grantWindowFocus(%b)", TAG, grantFocus); - - try { - WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, - mSystemWindows.getFocusGrantToken(mPipMenuView), grantFocus); - } catch (Exception e) { - ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Unable to update focus, %s", TAG, e); - } - } - private class PipMenuSurfaceChangedCallback implements ViewRootImpl.SurfaceChangedCallback { private final View mView; private final int mZOrder; 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 613791ccc062..57439a59ccca 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 @@ -55,7 +55,7 @@ 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.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.List; @@ -328,7 +328,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L return menuUiBounds; } - void transitionToMenuMode(int menuMode, boolean resetMenu) { + void transitionToMenuMode(int menuMode) { switch (menuMode) { case MODE_NO_MENU: hideAllUserControls(); @@ -337,7 +337,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L showMoveMenu(); break; case MODE_ALL_ACTIONS_MENU: - showAllActionsMenu(resetMenu); + showAllActionsMenu(); break; default: throw new IllegalArgumentException( @@ -362,13 +362,13 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L mEduTextDrawer.closeIfNeeded(); } - private void showAllActionsMenu(boolean resetMenu) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: showAllActionsMenu(), resetMenu %b", TAG, resetMenu); + void resetMenu() { + scrollToFirstAction(); + } - if (resetMenu) { - scrollToFirstAction(); - } + private void showAllActionsMenu() { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: showAllActionsMenu()", TAG); if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) return; @@ -431,12 +431,6 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L } } - @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - super.onWindowFocusChanged(hasWindowFocus); - mListener.onPipWindowFocusChanged(hasWindowFocus); - } - private void animateAlphaTo(float alpha, View view) { if (view.getAlpha() == alpha) { return; @@ -483,28 +477,28 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L if (event.getAction() == ACTION_UP) { if (event.getKeyCode() == KEYCODE_BACK) { - mListener.onBackPress(); + mListener.onExitCurrentMenuMode(); return true; } - if (mA11yManager.isEnabled()) { - return super.dispatchKeyEvent(event); - } - - switch (event.getKeyCode()) { - case KEYCODE_DPAD_UP: - case KEYCODE_DPAD_DOWN: - case KEYCODE_DPAD_LEFT: - case KEYCODE_DPAD_RIGHT: - return mListener.onPipMovement(event.getKeyCode()) || super.dispatchKeyEvent( - event); - case KEYCODE_ENTER: - case KEYCODE_DPAD_CENTER: - return mListener.onExitMoveMode() || super.dispatchKeyEvent(event); - default: - break; + if (mCurrentMenuMode == MODE_MOVE_MENU && !mA11yManager.isEnabled()) { + switch (event.getKeyCode()) { + case KEYCODE_DPAD_UP: + case KEYCODE_DPAD_DOWN: + case KEYCODE_DPAD_LEFT: + case KEYCODE_DPAD_RIGHT: + mListener.onPipMovement(event.getKeyCode()); + return true; + case KEYCODE_ENTER: + case KEYCODE_DPAD_CENTER: + mListener.onExitCurrentMenuMode(); + return true; + default: + // Dispatch key event as normal below + } } } + return super.dispatchKeyEvent(event); } @@ -529,7 +523,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L if (a11yEnabled) { mA11yDoneButton.setVisibility(VISIBLE); mA11yDoneButton.setOnClickListener(v -> { - mListener.onExitMoveMode(); + mListener.onExitCurrentMenuMode(); }); mA11yDoneButton.requestFocus(); mA11yDoneButton.requestAccessibilityFocus(); @@ -626,26 +620,15 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L interface Listener { - void onBackPress(); - - /** - * Called when a button for exiting move mode was pressed. - * - * @return true if the event was handled or false if the key event should be handled by the - * next receiver. - */ - boolean onExitMoveMode(); - /** - * @return whether pip movement was handled. + * Called when a button for exiting the current menu mode was pressed. */ - boolean onPipMovement(int keycode); + void onExitCurrentMenuMode(); /** - * Called when the TvPipMenuView loses focus. This also means that the TV PiP menu window - * has lost focus. + * Called when a button to move the PiP in a certain direction, indicated by keycode. */ - void onPipWindowFocusChanged(boolean focused); + void onPipMovement(int keycode); /** * The edu text closing impacts the size of the Picture-in-Picture window and influences 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 f22ee595e6c9..1c94625ddde9 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 @@ -36,9 +36,9 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ImageUtils; import com.android.wm.shell.R; -import com.android.wm.shell.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.List; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java index 4819f665d6d3..f315afba9a03 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java @@ -25,18 +25,18 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUiEventLogger; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.splitscreen.SplitScreenController; import java.util.Objects; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java index d3253a5e4d94..f24b2b385cad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java @@ -21,8 +21,8 @@ import android.content.Context; import androidx.annotation.NonNull; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTransition; import com.android.wm.shell.pip.PipTransitionState; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS new file mode 100644 index 000000000000..6dabb3bf6f9a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS @@ -0,0 +1,4 @@ +# WM shell sub-module pip owner +hwwang@google.com +mateuszc@google.com +gabiyev@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java new file mode 100644 index 000000000000..b8e4c04ac262 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java @@ -0,0 +1,81 @@ +/* + * 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.pip2; + +import android.annotation.NonNull; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import androidx.annotation.Nullable; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.pip.PipMenuController; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +/** Placeholder, for demonstrate purpose only. */ +public class PipTransition extends PipTransitionController { + public PipTransition( + @NonNull ShellInit shellInit, + @NonNull ShellTaskOrganizer shellTaskOrganizer, + @NonNull Transitions transitions, + PipBoundsState pipBoundsState, + PipMenuController pipMenuController, + PipBoundsAlgorithm pipBoundsAlgorithm) { + super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, + pipBoundsAlgorithm); + } + + @Override + protected void onInit() { + if (PipUtils.isPip2ExperimentEnabled()) { + mTransitions.addHandler(this); + } + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + return null; + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + return false; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) {} + + @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishT) {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md new file mode 100644 index 000000000000..415ea8f959cc --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md @@ -0,0 +1,3 @@ +# PLACEHOLDER + +This is meant to be the doc for the pip2 package.
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index 2a61445b27ba..05e4af3302af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -35,7 +35,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { WM_SHELL_RECENTS_TRANSITION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, "ShellRecents"), WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, - Consts.TAG_WM_SHELL), + "ShellDragAndDrop"), WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_STARTING_WINDOW), WM_SHELL_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, @@ -49,12 +49,14 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM_SPLIT_SCREEN), WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), - WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM_SHELL), WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), + WM_SHELL_BUBBLES(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + "Bubbles"), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); private final boolean mEnabled; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index f35eda6caef0..94e1b33dbf58 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -203,6 +203,17 @@ public class RecentTasksController implements TaskStackListenerCallback, } } + @Nullable + public SplitBounds getSplitBoundsForTaskId(int taskId) { + if (taskId == INVALID_TASK_ID) { + return null; + } + + // We could do extra verification of requiring both taskIds of a pair and verifying that + // the same split bounds object is returned... but meh. Seems unnecessary. + return mTaskSplitBoundsMap.get(taskId); + } + @Override public Context getContext() { return mContext; @@ -329,7 +340,7 @@ public class RecentTasksController implements TaskStackListenerCallback, continue; } - if (DesktopModeStatus.isProto2Enabled() && mDesktopModeTaskRepository.isPresent() + if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent() && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) { // Freeform tasks will be added as a separate entry freeformTasks.add(taskInfo); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 843e5af67af9..e916a140e5d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -16,6 +16,7 @@ package com.android.wm.shell.recents; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.view.WindowManager.TRANSIT_CHANGE; @@ -23,6 +24,8 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; + import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; @@ -69,6 +72,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private final Transitions mTransitions; private final ShellExecutor mExecutor; + @Nullable + private final RecentTasksController mRecentTasksController; private IApplicationThread mAnimApp = null; private final ArrayList<RecentsController> mControllers = new ArrayList<>(); @@ -82,6 +87,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { @Nullable RecentTasksController recentTasksController) { mTransitions = transitions; mExecutor = transitions.getMainExecutor(); + mRecentTasksController = recentTasksController; if (!Transitions.ENABLE_SHELL_TRANSITIONS) return; if (recentTasksController == null) return; shellInit.addInitCallback(() -> { @@ -165,13 +171,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { return false; } final RecentsController controller = mControllers.get(controllerIdx); - Transitions.setRunningRemoteTransitionDelegate(mAnimApp); + final IApplicationThread animApp = mAnimApp; mAnimApp = null; if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsTransitionHandler.startAnimation: failed to start animation"); return false; } + Transitions.setRunningRemoteTransitionDelegate(animApp); return true; } @@ -219,6 +226,13 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private ArrayList<TaskState> mPausingTasks = null; /** + * List of tasks were pausing but closed in a subsequent merged transition. If a + * closing task is reopened, the leash is not initially hidden since it is already + * visible. + */ + private ArrayList<TaskState> mClosingTasks = null; + + /** * List of tasks that we are switching to. Upon finish, these will remain visible and * on top. */ @@ -369,6 +383,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } mFinishTransaction = null; mPausingTasks = null; + mClosingTasks = null; mOpeningTasks = null; mInfo = null; mTransition = null; @@ -413,10 +428,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { mFinishCB = finishCB; mFinishTransaction = finishT; mPausingTasks = new ArrayList<>(); + mClosingTasks = new ArrayList<>(); mOpeningTasks = new ArrayList<>(); mLeashMap = new ArrayMap<>(); mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0; + int closingSplitTaskId = INVALID_TASK_ID; final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>(); final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>(); TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter(); @@ -443,6 +460,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { apps.add(target); if (TransitionUtil.isClosingType(change.getMode())) { mPausingTasks.add(new TaskState(change, target.leash)); + closingSplitTaskId = change.getTaskInfo().taskId; if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " adding pausing leaf home taskId=%d", taskInfo.taskId); @@ -500,13 +518,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } t.apply(); + Bundle b = new Bundle(1 /*capacity*/); + b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS, + mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId)); try { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.start: calling onAnimationStart", mInstanceId); mListener.onAnimationStart(this, apps.toArray(new RemoteAnimationTarget[apps.size()]), wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]), - new Rect(0, 0, 0, 0), new Rect()); + new Rect(0, 0, 0, 0), new Rect(), b); } catch (RemoteException e) { Slog.e(TAG, "Error starting recents animation", e); cancel("onAnimationStart() failed"); @@ -567,15 +588,18 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { // Tasks that are always on top (e.g. bubbles), will handle their own transition // as they are on top of everything else. So cancel the merge here. - cancel("task #" + taskInfo.taskId + " is always_on_top"); + cancel(false /* toHome */, false /* withScreenshots */, + "task #" + taskInfo.taskId + " is always_on_top"); return; } final boolean isRootTask = taskInfo != null && TransitionInfo.isIndependent(change, info); + final boolean isRecentsTask = mRecentsTask != null + && mRecentsTask.equals(change.getContainer()); hasTaskChange = hasTaskChange || isRootTask; final boolean isLeafTask = leafTaskFilter.test(change); if (TransitionUtil.isOpeningType(change.getMode())) { - if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) { + if (isRecentsTask) { recentsOpening = change; } else if (isRootTask || isLeafTask) { if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { @@ -590,7 +614,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { openingTaskIsLeafs.add(isLeafTask ? 1 : 0); } } else if (TransitionUtil.isClosingType(change.getMode())) { - if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) { + if (isRecentsTask) { foundRecentsClosing = true; } else if (isRootTask || isLeafTask) { if (closingTasks == null) { @@ -601,7 +625,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } else if (change.getMode() == TRANSIT_CHANGE) { // Finish recents animation if the display is changed, so the default // transition handler can play the animation such as rotation effect. - if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) { + if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY) + && info.getType() == TRANSIT_CHANGE) { // This call to cancel will use the screenshots taken preemptively in // handleMidTransitionRequest() prior to the display changing cancel(mWillFinishToHome, true /* withScreenshots */, "display change"); @@ -611,7 +636,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { if (!TransitionUtil.isOrderOnly(change) && isLeafTask) { hasChangingApp = true; } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME - && !mRecentsTask.equals(change.getContainer())) { + && !isRecentsTask ) { // Unless it is a 3p launcher. This means that the 3p launcher was already // visible (eg. the "pausing" task is translucent over the 3p launcher). // Treat it as if we are "re-opening" the 3p launcher. @@ -655,7 +680,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { final TransitionInfo.Change change = closingTasks.get(i); final int pausingIdx = TaskState.indexOf(mPausingTasks, change); if (pausingIdx >= 0) { - mPausingTasks.remove(pausingIdx); + // We are closing the pausing task, but it is still visible and can be + // restart by another transition prior to this transition finishing + final TaskState closingTask = mPausingTasks.remove(pausingIdx); + mClosingTasks.add(closingTask); didMergeThings = true; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " closing pausing taskId=%d", change.getTaskInfo().taskId); @@ -691,7 +719,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { for (int i = 0; i < openingTasks.size(); ++i) { final TransitionInfo.Change change = openingTasks.get(i); final boolean isLeaf = openingTaskIsLeafs.get(i) == 1; - int pausingIdx = TaskState.indexOf(mPausingTasks, change); + final int closingIdx = TaskState.indexOf(mClosingTasks, change); + if (closingIdx >= 0) { + // Remove opening tasks from closing set + mClosingTasks.remove(closingIdx); + } + final int pausingIdx = TaskState.indexOf(mPausingTasks, change); if (pausingIdx >= 0) { // Something is showing/opening a previously-pausing app. if (isLeaf) { @@ -714,12 +747,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { appearedTargets[nextTargetIdx++] = target; // reparent into the original `mInfo` since that's where we are animating. final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo); + final boolean wasClosing = closingIdx >= 0; t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash()); t.setLayer(target.leash, layer); - // Hide the animation leash, let listener show it. - t.hide(target.leash); + // Hide the animation leash if not already visible, let listener show it + t.setVisibility(target.leash, !wasClosing); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - " opening new leaf taskId=%d", target.taskId); + " opening new leaf taskId=%d wasClosing=%b", + target.taskId, wasClosing); mOpeningTasks.add(new TaskState(change, target.leash)); } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, @@ -767,7 +802,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { Slog.e(TAG, "Error sending appeared tasks to recents animation", e); } } - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); } /** For now, just set-up a jump-cut to the new activity. */ @@ -933,7 +968,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } cleanUp(); - finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */); + finishCB.onTransitionFinished(wct.isEmpty() ? null : wct); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index c414e708b28d..14304a3c0aac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -27,6 +27,7 @@ import android.view.RemoteAnimationTarget; import android.window.RemoteTransition; import com.android.wm.shell.splitscreen.ISplitScreenListener; +import com.android.wm.shell.splitscreen.ISplitSelectListener; /** * Interface that is exposed to remote callers to manipulate the splitscreen feature. @@ -44,6 +45,16 @@ interface ISplitScreen { oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2; /** + * Registers a split select listener. + */ + oneway void registerSplitSelectListener(in ISplitSelectListener listener) = 20; + + /** + * Unregisters a split select listener. + */ + oneway void unregisterSplitSelectListener(in ISplitSelectListener listener) = 21; + + /** * Removes a task from the side stage. */ oneway void removeFromSideStage(int taskId) = 4; @@ -148,4 +159,4 @@ interface ISplitScreen { */ RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14; } -// Last id = 19
\ No newline at end of file +// Last id = 21
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl new file mode 100644 index 000000000000..a25f39148b89 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl @@ -0,0 +1,29 @@ +/* + * 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.splitscreen; + +import android.app.ActivityManager.RunningTaskInfo; +import android.graphics.Rect; +/** + * Listener interface that Launcher attaches to SystemUI to get split-select callbacks. + */ +interface ISplitSelectListener { + /** + * Called when a task requests to enter split select + */ + boolean onRequestSplitSelect(in RunningTaskInfo taskInfo, int splitPosition, in Rect taskBounds); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index 89538cb394d4..e52235fda80f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -24,6 +24,9 @@ import android.window.WindowContainerTransaction; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; + +import java.util.Optional; /** * Main stage for split-screen mode. When split-screen is active all standard activity types launch @@ -35,9 +38,10 @@ class MainStage extends StageTaskListener { MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider) { + SurfaceSession surfaceSession, IconProvider iconProvider, + Optional<WindowDecorViewModel> windowDecorViewModel) { super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, - iconProvider); + iconProvider, windowDecorViewModel); } boolean isActive() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 8639b36faf4c..9903113c5453 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -25,6 +25,9 @@ import android.window.WindowContainerTransaction; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; + +import java.util.Optional; /** * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up @@ -37,9 +40,10 @@ class SideStage extends StageTaskListener { SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider) { + SurfaceSession surfaceSession, IconProvider iconProvider, + Optional<WindowDecorViewModel> windowDecorViewModel) { super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, - iconProvider); + iconProvider, windowDecorViewModel); } boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 2f2bc77b804b..ad4049320d93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen; import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.ActivityManager; import android.graphics.Rect; import com.android.wm.shell.common.annotations.ExternalThread; @@ -63,6 +64,14 @@ public interface SplitScreen { default void onSplitVisibilityChanged(boolean visible) {} } + /** Callback interface for listening to requests to enter split select */ + interface SplitSelectListener { + default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, + int splitPosition, Rect taskBounds) { + return false; + } + } + /** Registers listener that gets split screen callback. */ void registerSplitScreenListener(@NonNull SplitScreenListener listener, @NonNull Executor executor); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index e7a367f1992d..f70b0fc5af48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -23,7 +23,6 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; - import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; 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; @@ -31,6 +30,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage; +import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; @@ -42,6 +42,7 @@ import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.PendingIntent; import android.app.TaskInfo; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; @@ -50,6 +51,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; +import android.util.Log; import android.util.Slog; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; @@ -76,6 +78,7 @@ 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.ExternalInterfaceBinder; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; @@ -84,6 +87,7 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.common.split.SplitScreenUtils; +import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -94,12 +98,14 @@ 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 com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Optional; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; /** * Class manages split-screen multitasking mode and implements the main interface @@ -124,6 +130,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9; public static final int EXIT_REASON_RECREATE_SPLIT = 10; public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 11; + public static final int EXIT_REASON_ENTER_DESKTOP = 12; @IntDef(value = { EXIT_REASON_UNKNOWN, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, @@ -137,6 +144,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, EXIT_REASON_CHILD_TASK_ENTER_PIP, EXIT_REASON_RECREATE_SPLIT, EXIT_REASON_FULLSCREEN_SHORTCUT, + EXIT_REASON_ENTER_DESKTOP }) @Retention(RetentionPolicy.SOURCE) @interface ExitReason{} @@ -171,6 +179,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final TransactionPool mTransactionPool; private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; + private final LaunchAdjacentController mLaunchAdjacentController; + private final Optional<WindowDecorViewModel> mWindowDecorViewModel; + private final Optional<DesktopTasksController> mDesktopTasksController; private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler; private final String[] mAppsSupportMultiInstances; @@ -197,6 +208,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, + Optional<WindowDecorViewModel> windowDecorViewModel, + Optional<DesktopTasksController> desktopTasksController, ShellExecutor mainExecutor) { mShellCommandHandler = shellCommandHandler; mShellController = shellController; @@ -213,6 +227,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mTransactionPool = transactionPool; mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; + mLaunchAdjacentController = launchAdjacentController; + mWindowDecorViewModel = windowDecorViewModel; + mDesktopTasksController = desktopTasksController; mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this); // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic // override for this controller from the base module @@ -242,6 +259,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, TransactionPool transactionPool, IconProvider iconProvider, RecentTasksController recentTasks, + LaunchAdjacentController launchAdjacentController, + WindowDecorViewModel windowDecorViewModel, + DesktopTasksController desktopTasksController, ShellExecutor mainExecutor, StageCoordinator stageCoordinator) { mShellCommandHandler = shellCommandHandler; @@ -259,6 +279,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mTransactionPool = transactionPool; mIconProvider = iconProvider; mRecentTasksOptional = Optional.of(recentTasks); + mLaunchAdjacentController = launchAdjacentController; + mWindowDecorViewModel = Optional.of(windowDecorViewModel); + mDesktopTasksController = Optional.of(desktopTasksController); mStageCoordinator = stageCoordinator; mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this); shellInit.addInitCallback(this::onInit, this); @@ -291,13 +314,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator = createStageCoordinator(); } mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this)); + mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this)); + mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this)); } protected StageCoordinator createStageCoordinator() { return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mDisplayController, mDisplayImeController, - mDisplayInsetsController, mTransitions, mTransactionPool, - mIconProvider, mMainExecutor, mRecentTasksOptional); + mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, + mMainExecutor, mRecentTasksOptional, mLaunchAdjacentController, + mWindowDecorViewModel); } @Override @@ -407,8 +433,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, * transition. */ public void prepareExitSplitScreen(WindowContainerTransaction wct, - @StageType int stageToTop) { + @StageType int stageToTop, @ExitReason int reason) { mStageCoordinator.prepareExitSplitScreen(stageToTop, wct); + mStageCoordinator.clearSplitPairedInRecents(reason); } public void enterSplitScreen(int taskId, boolean leftOrTop) { @@ -451,6 +478,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.unregisterSplitScreenListener(listener); } + /** Register a split select listener */ + public void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) { + mStageCoordinator.registerSplitSelectListener(listener); + } + + /** Unregister a split select listener */ + public void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) { + mStageCoordinator.unregisterSplitSelectListener(listener); + } + public void goToFullscreenFromSplit() { mStageCoordinator.goToFullscreenFromSplit(); } @@ -468,6 +505,18 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator.getActivateSplitPosition(taskInfo); } + /** + * Move a task to split select + * @param taskInfo the task being moved to split select + * @param wct transaction to apply if this is a valid request + * @param splitPosition the split position this task should move to + * @param taskBounds current freeform bounds of the task entering split + */ + public void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, + WindowContainerTransaction wct, int splitPosition, Rect taskBounds) { + mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds); + } + public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { final int[] result = new int[1]; IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { @@ -537,6 +586,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startShortcut", + "app package " + packageName + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); return; @@ -566,6 +617,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, taskId = INVALID_TASK_ID; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startShortcutAndTaskWithLegacyTransition", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } @@ -598,12 +651,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, taskId = INVALID_TASK_ID; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startShortcutAndTask", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } } - mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2, - splitPosition, splitRatio, remoteTransition, instanceId); + mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId, + options2, splitPosition, splitRatio, remoteTransition, instanceId); } /** @@ -633,6 +688,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, taskId = INVALID_TASK_ID; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startIntentAndTaskWithLegacyTransition", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } @@ -663,6 +720,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, taskId = INVALID_TASK_ID; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startIntentAndTask", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } @@ -691,6 +750,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, pendingIntent2 = null; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startIntentsWithLegacyTransition", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } @@ -709,24 +770,38 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, Intent fillInIntent2 = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1); final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2); + final ActivityOptions activityOptions1 = options1 != null + ? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic(); + final ActivityOptions activityOptions2 = options2 != null + ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic(); if (samePackage(packageName1, packageName2, userId1, userId2)) { if (supportMultiInstancesSplit(packageName1)) { fillInIntent1 = new Intent(); fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); fillInIntent2 = new Intent(); fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + + if (shortcutInfo1 != null) { + activityOptions1.setApplyMultipleTaskFlagForShortcut(true); + } + if (shortcutInfo2 != null) { + activityOptions2.setApplyMultipleTaskFlagForShortcut(true); + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { pendingIntent2 = null; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startIntents", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } } - mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1, - pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio, - remoteTransition, instanceId); + mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, + activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2, + activityOptions2.toBundle(), splitPosition, splitRatio, remoteTransition, + instanceId); } @Override @@ -741,21 +816,22 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, final String packageName1 = SplitScreenUtils.getPackageName(intent); final String packageName2 = getPackageName(reverseSplitPosition(position)); final int userId2 = getUserId(reverseSplitPosition(position)); + final ComponentName component = intent.getIntent().getComponent(); + + // To prevent accumulating large number of instances in the background, reuse task + // in the background. If we don't explicitly reuse, new may be created even if the app + // isn't multi-instance because WM won't automatically remove/reuse the previous instance + final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional + .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1)) + .orElse(null); + if (taskInfo != null) { + startTask(taskInfo.taskId, position, options); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Start task in background"); + return; + } if (samePackage(packageName1, packageName2, userId1, userId2)) { if (supportMultiInstancesSplit(packageName1)) { - // To prevent accumulating large number of instances in the background, reuse task - // in the background with priority. - final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional - .map(recentTasks -> recentTasks.findTaskInBackground( - intent.getIntent().getComponent(), userId1)) - .orElse(null); - if (taskInfo != null) { - startTask(taskInfo.taskId, position, options); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, - "Start task in background"); - return; - } - // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of // the split and there is no reusable background task. fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); @@ -766,6 +842,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startIntent", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); return; @@ -934,6 +1012,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return "CHILD_TASK_ENTER_PIP"; case EXIT_REASON_RECREATE_SPLIT: return "RECREATE_SPLIT"; + case EXIT_REASON_ENTER_DESKTOP: + return "ENTER_DESKTOP"; default: return "unknown reason, reason int = " + exitReason; } @@ -1043,6 +1123,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private SplitScreenController mController; private final SingleInstanceRemoteListener<SplitScreenController, ISplitScreenListener> mListener; + private final SingleInstanceRemoteListener<SplitScreenController, + ISplitSelectListener> mSelectListener; private final SplitScreen.SplitScreenListener mSplitScreenListener = new SplitScreen.SplitScreenListener() { @Override @@ -1056,11 +1138,27 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } }; + private final SplitScreen.SplitSelectListener mSplitSelectListener = + new SplitScreen.SplitSelectListener() { + @Override + public boolean onRequestEnterSplitSelect( + ActivityManager.RunningTaskInfo taskInfo, int splitPosition, + Rect taskBounds) { + AtomicBoolean result = new AtomicBoolean(false); + mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo, + splitPosition, taskBounds))); + return result.get(); + } + }; + public ISplitScreenImpl(SplitScreenController controller) { mController = controller; mListener = new SingleInstanceRemoteListener<>(controller, c -> c.registerSplitScreenListener(mSplitScreenListener), c -> c.unregisterSplitScreenListener(mSplitScreenListener)); + mSelectListener = new SingleInstanceRemoteListener<>(controller, + c -> c.registerSplitSelectListener(mSplitSelectListener), + c -> c.unregisterSplitSelectListener(mSplitSelectListener)); } /** @@ -1086,6 +1184,18 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override + public void registerSplitSelectListener(ISplitSelectListener listener) { + executeRemoteCallWithTaskPermission(mController, "registerSplitSelectListener", + (controller) -> mSelectListener.register(listener)); + } + + @Override + public void unregisterSplitSelectListener(ISplitSelectListener listener) { + executeRemoteCallWithTaskPermission(mController, "unregisterSplitSelectListener", + (controller) -> mSelectListener.unregister()); + } + + @Override public void exitSplitScreen(int toTopTaskId) { executeRemoteCallWithTaskPermission(mController, "exitSplitScreen", (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index d21f8a48e62a..7dec12aac39b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -43,7 +43,6 @@ import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.TransactionPool; @@ -109,7 +108,7 @@ class SplitScreenTransitions { if (pendingTransition.mCanceled) { // The pending transition was canceled, so skip playing animation. startTransaction.apply(); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); return; } @@ -211,7 +210,7 @@ class SplitScreenTransitions { } } t.apply(); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); } /** Play animation for drag divider dismiss transition. */ @@ -238,7 +237,7 @@ class SplitScreenTransitions { mAnimations.remove(va); if (animated) { mTransitions.getMainExecutor().execute(() -> { - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); }); } }); @@ -250,7 +249,7 @@ class SplitScreenTransitions { } } startTransaction.apply(); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); } /** Play animation for resize transition. */ @@ -283,7 +282,7 @@ class SplitScreenTransitions { mAnimations.remove(va); if (animated) { mTransitions.getMainExecutor().execute(() -> { - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); }); } }); @@ -291,7 +290,7 @@ class SplitScreenTransitions { } startTransaction.apply(); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); } boolean isPendingTransition(IBinder transition) { @@ -325,8 +324,10 @@ class SplitScreenTransitions { void startFullscreenTransition(WindowContainerTransaction wct, @Nullable RemoteTransition handler) { - mTransitions.startTransition(TRANSIT_OPEN, wct, - new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler)); + OneShotRemoteHandler fullscreenHandler = + new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler); + fullscreenHandler.setTransition(mTransitions + .startTransition(TRANSIT_OPEN, wct, fullscreenHandler)); } @@ -391,7 +392,7 @@ class SplitScreenTransitions { if (mPendingResize != null) { mPendingResize.cancel(null); mAnimations.clear(); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); } IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler); @@ -450,7 +451,7 @@ class SplitScreenTransitions { } } - void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { + void onFinish(WindowContainerTransaction wct) { if (!mAnimations.isEmpty()) return; if (wct == null) wct = new WindowContainerTransaction(); @@ -470,7 +471,7 @@ class SplitScreenTransitions { mOnFinish.run(); if (mFinishCallback != null) { - mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */); + mFinishCallback.onTransitionFinished(wct /* wct */); mFinishCallback = null; } } @@ -495,7 +496,7 @@ class SplitScreenTransitions { mTransactionPool.release(transaction); mTransitions.getMainExecutor().execute(() -> { mAnimations.remove(va); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); }); } }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java index 5483fa5d29f6..f4ab2266179a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java @@ -25,6 +25,7 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED; @@ -42,6 +43,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME; @@ -192,6 +194,8 @@ public class SplitscreenEventLogger { return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT; case EXIT_REASON_FULLSCREEN_SHORTCUT: return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT; + case EXIT_REASON_ENTER_DESKTOP: + return SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP; case EXIT_REASON_UNKNOWN: // Fall through default: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 7699e2f7c13d..94fa485efd5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -28,6 +28,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; @@ -41,6 +42,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; +import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; @@ -81,7 +83,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; -import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.devicestate.DeviceStateManager; import android.os.Bundle; @@ -121,6 +122,7 @@ 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.LaunchAdjacentController; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; @@ -138,11 +140,14 @@ import com.android.wm.shell.transition.LegacyTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.SplitBounds; import com.android.wm.shell.util.TransitionUtil; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; /** * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and @@ -182,6 +187,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final ShellTaskOrganizer mTaskOrganizer; private final Context mContext; private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>(); + private final Set<SplitScreen.SplitSelectListener> mSelectListeners = new HashSet<>(); private final DisplayController mDisplayController; private final DisplayImeController mDisplayImeController; private final DisplayInsetsController mDisplayInsetsController; @@ -193,6 +199,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // if user is opening another task(s). private final ArrayList<Integer> mPausingTasks = new ArrayList<>(); private final Optional<RecentTasksController> mRecentTasks; + private final LaunchAdjacentController mLaunchAdjacentController; + private final Optional<WindowDecorViewModel> mWindowDecorViewModel; private final Rect mTempRect1 = new Rect(); private final Rect mTempRect2 = new Rect(); @@ -213,11 +221,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private boolean mIsDropEntering; private boolean mIsExiting; private boolean mIsRootTranslucent; + @VisibleForTesting + int mTopStageAfterFoldDismiss; private DefaultMixedHandler mMixedHandler; private final Toast mSplitUnsupportedToast; private SplitRequest mSplitRequest; + /** + * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support + * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage. + */ + @Override + public boolean supportCompatUI() { + return false; + } + class SplitRequest { @SplitPosition int mActivatePosition; @@ -270,7 +289,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks) { + Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, + Optional<WindowDecorViewModel> windowDecorViewModel) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -278,6 +299,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLogger = new SplitscreenEventLogger(); mMainExecutor = mainExecutor; mRecentTasks = recentTasks; + mLaunchAdjacentController = launchAdjacentController; + mWindowDecorViewModel = windowDecorViewModel; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); @@ -288,7 +311,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStageListener, mSyncQueue, mSurfaceSession, - iconProvider); + iconProvider, + mWindowDecorViewModel); mSideStage = new SideStage( mContext, mTaskOrganizer, @@ -296,7 +320,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStageListener, mSyncQueue, mSurfaceSession, - iconProvider); + iconProvider, + mWindowDecorViewModel); mDisplayController = displayController; mDisplayImeController = displayImeController; mDisplayInsetsController = displayInsetsController; @@ -323,7 +348,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayInsetsController displayInsetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks) { + Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, + Optional<WindowDecorViewModel> windowDecorViewModel) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -340,6 +367,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLogger = new SplitscreenEventLogger(); mMainExecutor = mainExecutor; mRecentTasks = recentTasks; + mLaunchAdjacentController = launchAdjacentController; + mWindowDecorViewModel = windowDecorViewModel; mDisplayController.addDisplayWindowListener(this); transitions.addHandler(this); mSplitUnsupportedToast = Toast.makeText(mContext, @@ -445,6 +474,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mLogger; } + void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, + WindowContainerTransaction wct, int splitPosition, Rect taskBounds) { + boolean enteredSplitSelect = false; + for (SplitScreen.SplitSelectListener listener : mSelectListeners) { + enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo, splitPosition, + taskBounds); + } + if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct); + } + void startShortcut(String packageName, String shortcutId, @SplitPosition int position, Bundle options, UserHandle user) { final boolean isEnteringSplit = !isSplitActive(); @@ -459,6 +498,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isEnteringSplit && mSideStage.getChildCount() == 0) { mMainExecutor.execute(() -> exitSplitScreen( null /* childrenToTop */, EXIT_REASON_UNKNOWN)); + Log.w(TAG, splitFailureMessage("startShortcut", + "side stage was not populated")); mSplitUnsupportedToast.show(); } @@ -546,6 +587,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isEnteringSplit && mSideStage.getChildCount() == 0) { mMainExecutor.execute(() -> exitSplitScreen( null /* childrenToTop */, EXIT_REASON_UNKNOWN)); + Log.w(TAG, splitFailureMessage("startIntentLegacy", + "side stage was not populated")); mSplitUnsupportedToast.show(); } @@ -718,12 +761,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.activate(wct, false /* reparent */); } + setSideStagePosition(splitPosition, wct); mSplitLayout.setDivideRatio(splitRatio); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); setRootForceTranslucent(false, wct); - setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); if (shortcutInfo1 != null) { @@ -1076,6 +1119,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled", + "main or side stage was not populated.")); mSplitUnsupportedToast.show(); } else { mSyncQueue.queue(evictWct); @@ -1095,6 +1140,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished", + "main or side stage was not populated")); mSplitUnsupportedToast.show(); return; } @@ -1279,20 +1326,30 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible; final boolean oneStageVisible = mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible; - if (oneStageVisible) { + if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) { // Dismiss split because there's show-when-locked activity showing on top of keyguard. // Also make sure the task contains show-when-locked activity remains on top after split // dismissed. - if (!ENABLE_SHELL_TRANSITIONS) { - final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage; - exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); - } else { - final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage; + exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); + } + + // Dismiss split if the flag record any side of stages. + if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { + if (ENABLE_SHELL_TRANSITIONS) { + // Need manually clear here due to this transition might be aborted due to keyguard + // on top and lead to no visible change. + clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED); final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(dismissTop, wct); - mSplitTransitions.startDismissTransition(wct, this, dismissTop, - EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); + prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct); + mSplitTransitions.startDismissTransition(wct, this, + mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); + } else { + exitSplitScreen( + mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, + EXIT_REASON_DEVICE_FOLDED); } + mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; } } @@ -1330,15 +1387,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!mMainStage.isActive() || mIsExiting) return; onSplitScreenExit(); + clearSplitPairedInRecents(exitReason); - mRecentTasks.ifPresent(recentTasks -> { - // Notify recents if we are exiting in a way that breaks the pair, and disable further - // updates to splits in the recents until we enter split again - if (shouldBreakPairedTaskInRecents(exitReason) && mShouldUpdateRecents) { - recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId()); - recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId()); - } - }); mShouldUpdateRecents = false; mIsDividerRemoteAnimating = false; mSplitRequest = null; @@ -1465,6 +1515,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + void clearSplitPairedInRecents(@ExitReason int exitReason) { + if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return; + + mRecentTasks.ifPresent(recentTasks -> { + // Notify recents if we are exiting in a way that breaks the pair, and disable further + // updates to splits in the recents until we enter split again + mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); + mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); + }); + } + /** * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates * an existing WindowContainerTransaction (rather than applying immediately). This is intended @@ -1547,6 +1608,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // split bounds. wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); + mSplitLayout.getInvisibleBounds(mTempRect1); + mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1); } wct.reorder(mRootTaskInfo.token, true); setRootForceTranslucent(false, wct); @@ -1616,6 +1679,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mListeners.remove(listener); } + void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) { + mSelectListeners.add(listener); + } + + void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) { + mSelectListeners.remove(listener); + } + void sendStatusToListener(SplitScreen.SplitScreenListener listener) { listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition()); listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition()); @@ -1733,12 +1804,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mRootTaskInfo == null || mRootTaskInfo.taskId != taskInfo.taskId) { throw new IllegalArgumentException(this + "\n Unknown task info changed: " + taskInfo); } - + mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); mRootTaskInfo = taskInfo; if (mSplitLayout != null && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration) - && mMainStage.isActive() - && !ENABLE_SHELL_TRANSITIONS) { + && mMainStage.isActive()) { // Clear the divider remote animating flag as the divider will be re-rendered to apply // the new rotation config. mIsDividerRemoteAnimating = false; @@ -1781,7 +1851,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true); // Make the stages adjacent to each other so they occlude what's behind them. wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); - wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); setRootForceTranslucent(true, wct); mSplitLayout.getInvisibleBounds(mTempRect1); wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); @@ -1789,6 +1858,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSyncQueue.runInSync(t -> { t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top); }); + mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token); } /** Callback when split roots have child task appeared under it, this is a little different from @@ -1818,9 +1888,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void onRootTaskVanished() { final WindowContainerTransaction wct = new WindowContainerTransaction(); - if (mRootTaskInfo != null) { - wct.clearLaunchAdjacentFlagRoot(mRootTaskInfo.token); - } + mLaunchAdjacentController.clearLaunchAdjacentRoot(); applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED); mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout); } @@ -2178,21 +2246,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayController.addDisplayChangingController(this::onDisplayChange); } - @Override - public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { - if (displayId != DEFAULT_DISPLAY) { - return; - } - if (mSplitLayout != null && mSplitLayout.isDensityChanged(newConfig.densityDpi) - && mMainStage.isActive() - && mSplitLayout.updateConfiguration(newConfig) - && ENABLE_SHELL_TRANSITIONS) { - mSplitLayout.update(null /* t */); - onLayoutSizeChanged(mSplitLayout); - } - } - - void updateSurfaces(SurfaceControl.Transaction transaction) { + /** + * Update surfaces of the split screen layout based on the current state + * @param transaction to write the updates to + */ + public void updateSurfaces(SurfaceControl.Transaction transaction) { updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false); mSplitLayout.update(transaction); } @@ -2211,26 +2269,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @VisibleForTesting void onFoldedStateChanged(boolean folded) { - int topStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; if (!folded) return; - if (!mMainStage.isActive()) return; + if (!isSplitActive() || !isSplitScreenVisible()) return; + // To avoid split dismiss when user fold the device and unfold to use later, we only + // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss + // when user interact on phone folded. if (mMainStage.isFocused()) { - topStageAfterFoldDismiss = STAGE_TYPE_MAIN; + mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN; } else if (mSideStage.isFocused()) { - topStageAfterFoldDismiss = STAGE_TYPE_SIDE; - } - - if (ENABLE_SHELL_TRANSITIONS) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(topStageAfterFoldDismiss, wct); - mSplitTransitions.startDismissTransition(wct, this, - topStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); - } else { - exitSplitScreen( - topStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, - EXIT_REASON_DEVICE_FOLDED); + mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE; } } @@ -2364,6 +2414,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // so appends operations to exit split. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); } + } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null + && isSplitScreenVisible()) { + // Split include show when lock activity case, check the top activity under which + // stage and move it to the top. + int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity) + ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + prepareExitSplitScreen(top, out); + mSplitTransitions.setDismissTransition(transition, top, + EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } // When split in the background, it should be only opening/dismissing transition and @@ -2519,6 +2578,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // so don't handle it. Log.e(TAG, "Somehow removed the last task in a stage outside of a proper " + "transition."); + // This new transition would be merged to current one so we need to clear + // tile manually here. + clearSplitPairedInRecents(EXIT_REASON_APP_FINISHED); final WindowContainerTransaction wct = new WindowContainerTransaction(); final int dismissTop = (dismissStages.size() == 1 && getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN) @@ -2688,19 +2750,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) { // Open to side should only be used when split already active and foregorund. if (mainChild == null && sideChild == null) { - Log.w(TAG, "Launched a task in split, but didn't receive any task in transition."); + Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", + "Launched a task in split, but didn't receive any task in transition.")); // This should happen when the target app is already on front, so just cancel. mSplitTransitions.mPendingEnter.cancel(null); return true; } } else { if (mainChild == null || sideChild == null) { - Log.w(TAG, "Launched 2 tasks in split, but didn't receive" - + " 2 tasks in transition. Possibly one of them failed to launch"); final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN : (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED); mSplitTransitions.mPendingEnter.cancel( (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct)); + Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", + "launched 2 tasks in split, but didn't receive " + + "2 tasks in transition. Possibly one of them failed to launch")); + if (mRecentTasks.isPresent() && mainChild != null) { + mRecentTasks.get().removeSplitPair(mainChild.getTaskInfo().taskId); + } + if (mRecentTasks.isPresent() && sideChild != null) { + mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId); + } mSplitUnsupportedToast.show(); return true; } @@ -3158,7 +3228,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onNoLongerSupportMultiWindow() { + public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) { if (mMainStage.isActive()) { final boolean isMainStage = mMainStageListener == this; if (!ENABLE_SHELL_TRANSITIONS) { @@ -3173,6 +3243,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareExitSplitScreen(stageType, wct); mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); + Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow", + "app package " + taskInfo.baseActivity.getPackageName() + + " does not support splitscreen, or is a controlled activity type")); mSplitUnsupportedToast.show(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index a01eddbc9b9f..af7bf360f036 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -51,8 +51,11 @@ import com.android.wm.shell.common.SurfaceUtils; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.splitscreen.SplitScreen.StageType; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.io.PrintWriter; +import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -79,7 +82,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { void onRootTaskVanished(); - void onNoLongerSupportMultiWindow(); + void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo); } private final Context mContext; @@ -87,6 +90,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { private final SurfaceSession mSurfaceSession; private final SyncTransactionQueue mSyncQueue; private final IconProvider mIconProvider; + private final Optional<WindowDecorViewModel> mWindowDecorViewModel; protected ActivityManager.RunningTaskInfo mRootTaskInfo; protected SurfaceControl mRootLeash; @@ -98,12 +102,14 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider) { + SurfaceSession surfaceSession, IconProvider iconProvider, + Optional<WindowDecorViewModel> windowDecorViewModel) { mContext = context; mCallbacks = callbacks; mSyncQueue = syncQueue; mSurfaceSession = surfaceSession; mIconProvider = iconProvider; + mWindowDecorViewModel = windowDecorViewModel; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); } @@ -202,6 +208,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); if (mRootTaskInfo.taskId == taskInfo.taskId) { // Inflates split decor view only when the root task is visible. if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) { @@ -220,7 +227,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { taskInfo.getWindowingMode())) { // Leave split screen if the task no longer supports multi window or have // uncontrolled task. - mCallbacks.onNoLongerSupportMultiWindow(); + mCallbacks.onNoLongerSupportMultiWindow(taskInfo); return; } mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); @@ -341,6 +348,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */); } + void doForAllChildTasks(Consumer<Integer> consumer) { + for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { + final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); + consumer.accept(taskInfo.taskId); + } + } + /** Collects all the current child tasks and prepares transaction to evict them to display. */ void evictAllChildren(WindowContainerTransaction wct) { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 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 index 27d520d81c41..c10142588bde 100644 --- 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 @@ -27,6 +27,7 @@ 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.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; @@ -58,6 +59,7 @@ public class TvSplitScreenController extends SplitScreenController { private final TransactionPool mTransactionPool; private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; + private final LaunchAdjacentController mLaunchAdjacentController; private final Handler mMainHandler; private final SystemWindows mSystemWindows; @@ -77,13 +79,15 @@ public class TvSplitScreenController extends SplitScreenController { TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, ShellExecutor mainExecutor, Handler mainHandler, SystemWindows systemWindows) { super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController, displayImeController, displayInsetsController, dragAndDropController, transitions, transactionPool, - iconProvider, recentTasks, mainExecutor); + iconProvider, recentTasks, launchAdjacentController, Optional.empty(), + Optional.empty(), mainExecutor); mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; @@ -96,6 +100,7 @@ public class TvSplitScreenController extends SplitScreenController { mTransactionPool = transactionPool; mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; + mLaunchAdjacentController = launchAdjacentController; mMainHandler = mainHandler; mSystemWindows = systemWindows; @@ -111,7 +116,7 @@ public class TvSplitScreenController extends SplitScreenController { mTaskOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, mMainExecutor, mMainHandler, - mRecentTasksOptional, mSystemWindows); + mRecentTasksOptional, mLaunchAdjacentController, 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 index 4d563fbb7f04..79476919221e 100644 --- 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 @@ -24,6 +24,7 @@ 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.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; @@ -51,10 +52,11 @@ public class TvStageCoordinator extends StageCoordinator IconProvider iconProvider, ShellExecutor mainExecutor, Handler mainHandler, Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, SystemWindows systemWindows) { super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController, displayInsetsController, transitions, transactionPool, iconProvider, - mainExecutor, recentTasks); + mainExecutor, recentTasks, launchAdjacentController, Optional.empty()); mTvSplitMenuController = new TvSplitMenuController(context, this, systemWindows, mainHandler); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java index 20c4d5ae5f58..e7e1e0a98550 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java @@ -35,13 +35,14 @@ class SnapshotWindowCreator { void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) { final int taskId = startingWindowInfo.taskInfo.taskId; // Remove any existing starting window for this task before adding. - mStartingWindowRecordManager.removeWindow(taskId, true); + mStartingWindowRecordManager.removeWindow(taskId); final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, startingWindowInfo.appToken, snapshot, mMainExecutor, - () -> mStartingWindowRecordManager.removeWindow(taskId, true)); + () -> mStartingWindowRecordManager.removeWindow(taskId)); if (surface != null) { final SnapshotWindowRecord tView = new SnapshotWindowRecord(surface, - startingWindowInfo.taskInfo.topActivityType, mMainExecutor); + startingWindowInfo.taskInfo.topActivityType, mMainExecutor, + taskId, mStartingWindowRecordManager); mStartingWindowRecordManager.addRecord(taskId, tView); } } @@ -50,8 +51,9 @@ class SnapshotWindowCreator { private final TaskSnapshotWindow mTaskSnapshotWindow; SnapshotWindowRecord(TaskSnapshotWindow taskSnapshotWindow, - int activityType, ShellExecutor removeExecutor) { - super(activityType, removeExecutor); + int activityType, ShellExecutor removeExecutor, int id, + StartingSurfaceDrawer.StartingWindowRecordManager recordManager) { + super(activityType, removeExecutor, id, recordManager); mTaskSnapshotWindow = taskSnapshotWindow; mBGColor = mTaskSnapshotWindow.getBackgroundColor(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java index 20da8773f387..edb5aba1e46b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java @@ -46,6 +46,8 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { private final int mIconFadeOutDuration; private final int mAppRevealDelay; private final int mAppRevealDuration; + @SplashScreenExitAnimationUtils.ExitAnimationType + private final int mAnimationType; private final int mAnimationDuration; private final float mIconStartAlpha; private final float mBrandingStartAlpha; @@ -91,6 +93,8 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { } mAppRevealDuration = context.getResources().getInteger( R.integer.starting_window_app_reveal_anim_duration); + mAnimationType = context.getResources().getInteger( + R.integer.starting_window_exit_animation_type); mAnimationDuration = Math.max(mIconFadeOutDuration, mAppRevealDelay + mAppRevealDuration); mMainWindowShiftLength = mainWindowShiftLength; mFinishCallback = handleFinish; @@ -98,10 +102,10 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { } void startAnimations() { - SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface, - mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration, - mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay, - mAppRevealDuration, this, mRoundedCornerRadius); + SplashScreenExitAnimationUtils.startAnimations(mAnimationType, mSplashScreenView, + mFirstWindowSurface, mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, + mAnimationDuration, mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, + mAppRevealDelay, mAppRevealDuration, this, mRoundedCornerRadius); } private void reset() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java index a7e4385b60c8..e86b62dee86d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java @@ -20,6 +20,7 @@ import static android.view.Choreographer.CALLBACK_COMMIT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.IntDef; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Configuration; @@ -54,6 +55,7 @@ import com.android.wm.shell.common.TransactionPool; public class SplashScreenExitAnimationUtils { private static final boolean DEBUG_EXIT_ANIMATION = false; private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false; + private static final boolean DEBUG_EXIT_FADE_ANIMATION = false; private static final String TAG = "SplashScreenExitAnimationUtils"; private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f); @@ -62,20 +64,47 @@ public class SplashScreenExitAnimationUtils { private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f); /** + * This splash screen exit animation type uses a radial vanish to hide + * the starting window and slides up the main window content. + * @hide + */ + public static final int TYPE_RADIAL_VANISH_SLIDE_UP = 0; + + /** + * This splash screen exit animation type fades out the starting window + * to reveal the main window content. + * @hide + */ + public static final int TYPE_FADE_OUT = 1; + + /** @hide */ + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_RADIAL_VANISH_SLIDE_UP, + TYPE_FADE_OUT, + }) + public @interface ExitAnimationType {} + + /** * Creates and starts the animator to fade out the icon, reveal the app, and shift up main * window with rounded corner radius. */ - static void startAnimations(ViewGroup splashScreenView, - SurfaceControl firstWindowSurface, int mainWindowShiftLength, - TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration, - int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha, - int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener, - float roundedCornerRadius) { - ValueAnimator animator = - createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength, - transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration, - iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration, - animatorListener, roundedCornerRadius); + static void startAnimations(@ExitAnimationType int animationType, + ViewGroup splashScreenView, SurfaceControl firstWindowSurface, + int mainWindowShiftLength, TransactionPool transactionPool, Rect firstWindowFrame, + int animationDuration, int iconFadeOutDuration, float iconStartAlpha, + float brandingStartAlpha, int appRevealDelay, int appRevealDuration, + Animator.AnimatorListener animatorListener, float roundedCornerRadius) { + ValueAnimator animator; + if (animationType == TYPE_FADE_OUT) { + animator = createFadeOutAnimation(splashScreenView, animationDuration, + iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, appRevealDelay, + appRevealDuration, animatorListener); + } else { + animator = createRadialVanishSlideUpAnimator(splashScreenView, + firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame, + animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, + appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius); + } animator.start(); } @@ -89,17 +118,18 @@ public class SplashScreenExitAnimationUtils { TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration, int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha, int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) { - startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength, - transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration, - iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration, - animatorListener, 0f /* roundedCornerRadius */); + // Start the default 'reveal' animation. + startAnimations(TYPE_RADIAL_VANISH_SLIDE_UP, splashScreenView, + firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame, + animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, + appRevealDelay, appRevealDuration, animatorListener, 0f /* roundedCornerRadius */); } /** * Creates the animator to fade out the icon, reveal the app, and shift up main window. * @hide */ - private static ValueAnimator createAnimator(ViewGroup splashScreenView, + private static ValueAnimator createRadialVanishSlideUpAnimator(ViewGroup splashScreenView, SurfaceControl firstWindowSurface, int mMainWindowShiftLength, TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration, int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha, @@ -210,6 +240,59 @@ public class SplashScreenExitAnimationUtils { return nightMode == Configuration.UI_MODE_NIGHT_YES; } + private static ValueAnimator createFadeOutAnimation(ViewGroup splashScreenView, + int animationDuration, int iconFadeOutDuration, float iconStartAlpha, + float brandingStartAlpha, int appRevealDelay, int appRevealDuration, + Animator.AnimatorListener animatorListener) { + + if (DEBUG_EXIT_FADE_ANIMATION) { + splashScreenView.setBackgroundColor(Color.BLUE); + } + + final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + animator.setDuration(animationDuration); + animator.setInterpolator(Interpolators.LINEAR); + animator.addUpdateListener(animation -> { + + float linearProgress = (float) animation.getAnimatedValue(); + + // Icon fade out progress (always starts immediately) + final float iconFadeProgress = ICON_INTERPOLATOR.getInterpolation(getProgress( + linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration)); + View iconView = null; + View brandingView = null; + + if (splashScreenView instanceof SplashScreenView) { + iconView = ((SplashScreenView) splashScreenView).getIconView(); + brandingView = ((SplashScreenView) splashScreenView).getBrandingView(); + } + if (iconView != null) { + iconView.setAlpha(iconStartAlpha * (1f - iconFadeProgress)); + } + if (brandingView != null) { + brandingView.setAlpha(brandingStartAlpha * (1f - iconFadeProgress)); + } + + // Splash screen fade out progress (possibly delayed) + final float splashFadeProgress = Interpolators.ALPHA_OUT.getInterpolation( + getProgress(linearProgress, appRevealDelay, + appRevealDuration, animationDuration)); + + splashScreenView.setAlpha(1f - splashFadeProgress); + + if (DEBUG_EXIT_FADE_ANIMATION) { + Slog.d(TAG, "progress -> animation: " + linearProgress + + "\t icon alpha: " + ((iconView != null) ? iconView.getAlpha() : "n/a") + + "\t splash alpha: " + splashScreenView.getAlpha() + ); + } + }); + if (animatorListener != null) { + animator.addListener(animatorListener); + } + return animator; + } + /** * View which creates a circular reveal of the underlying view. * @hide 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 dc91a11dc64f..0c6adc942385 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 @@ -112,28 +112,15 @@ public class SplashscreenContentDrawer { */ static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION; - // The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an - // icon which it's non-transparent foreground area is similar to it's background area, then - // do not enlarge the foreground drawable. - // For example, an icon with the foreground 108*108 opaque pixels and it's background - // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon. - private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f); - - /** - * If the developer doesn't specify a background for the icon, we slightly scale it up. - * - * The background is either manually specified in the theme or the Adaptive Icon - * background is used if it's different from the window background. - */ - private static final float NO_BACKGROUND_SCALE = 192f / 160; private final Context mContext; private final HighResIconProvider mHighResIconProvider; - private int mIconSize; private int mDefaultIconSize; private int mBrandingImageWidth; private int mBrandingImageHeight; private int mMainWindowShiftLength; + private float mEnlargeForegroundIconThreshold; + private float mNoBackgroundScale; private int mLastPackageContextConfigHash; private final TransactionPool mTransactionPool; private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs(); @@ -336,6 +323,10 @@ public class SplashscreenContentDrawer { com.android.wm.shell.R.dimen.starting_surface_brand_image_height); mMainWindowShiftLength = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length); + mEnlargeForegroundIconThreshold = mContext.getResources().getFloat( + com.android.wm.shell.R.dimen.splash_icon_enlarge_foreground_threshold); + mNoBackgroundScale = mContext.getResources().getFloat( + com.android.wm.shell.R.dimen.splash_icon_no_background_scale_factor); } /** @@ -385,8 +376,8 @@ public class SplashscreenContentDrawer { private static int estimateWindowBGColor(Drawable themeBGDrawable) { final DrawableColorTester themeBGTester = new DrawableColorTester( themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */); - if (themeBGTester.passFilterRatio() != 1) { - // the window background is translucent, unable to draw + if (themeBGTester.passFilterRatio() < 0.5f) { + // more than half pixels of the window background is translucent, unable to draw Slog.w(TAG, "Window background is translucent, fill background with black color"); return getSystemBGColor(); } else { @@ -604,14 +595,14 @@ public class SplashscreenContentDrawer { // There is no background below the icon, so scale the icon up if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT || mTmpAttrs.mIconBgColor == mThemeColor) { - mFinalIconSize *= NO_BACKGROUND_SCALE; + mFinalIconSize *= mNoBackgroundScale; } createIconDrawable(iconDrawable, false /* legacy */, false /* loadInDetail */); } else { final float iconScale = (float) mIconSize / (float) mDefaultIconSize; final int densityDpi = mContext.getResources().getConfiguration().densityDpi; final int scaledIconDpi = - (int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE); + (int) (0.5f + iconScale * densityDpi * mNoBackgroundScale); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon"); iconDrawable = mHighResIconProvider.getIcon( mActivityInfo, densityDpi, scaledIconDpi); @@ -693,8 +684,8 @@ public class SplashscreenContentDrawer { // Reference AdaptiveIcon description, outer is 108 and inner is 72, so we // scale by 192/160 if we only draw adaptiveIcon's foreground. final float noBgScale = - iconColor.mFgNonTranslucentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD - ? NO_BACKGROUND_SCALE : 1f; + iconColor.mFgNonTranslucentRatio < mEnlargeForegroundIconThreshold + ? mNoBackgroundScale : 1f; // Using AdaptiveIconDrawable here can help keep the shape consistent with the // current settings. mFinalIconSize = (int) (0.5f + mIconSize * noBgScale); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java index 4cfbbd971fe3..31fc98b713ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java @@ -358,7 +358,7 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator { } } if (shouldSaveView) { - mStartingWindowRecordManager.removeWindow(taskId, true); + mStartingWindowRecordManager.removeWindow(taskId); saveSplashScreenRecord(appToken, taskId, view, suggestType); } return shouldSaveView; 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 7cbf263f7cb1..e2be1533118a 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 @@ -188,7 +188,7 @@ public class StartingSurfaceDrawer { final SnapshotRecord record = sRecord instanceof SnapshotRecord ? (SnapshotRecord) sRecord : null; if (record != null && record.hasImeSurface()) { - records.removeWindow(taskId, true); + records.removeWindow(taskId); } } @@ -256,10 +256,15 @@ public class StartingSurfaceDrawer { @WindowConfiguration.ActivityType protected final int mActivityType; protected final ShellExecutor mRemoveExecutor; + private final int mTaskId; + private final StartingWindowRecordManager mRecordManager; - SnapshotRecord(int activityType, ShellExecutor removeExecutor) { + SnapshotRecord(int activityType, ShellExecutor removeExecutor, int taskId, + StartingWindowRecordManager recordManager) { mActivityType = activityType; mRemoveExecutor = removeExecutor; + mTaskId = taskId; + mRecordManager = recordManager; } @Override @@ -301,6 +306,7 @@ public class StartingSurfaceDrawer { @CallSuper protected void removeImmediately() { mRemoveExecutor.removeCallbacks(mScheduledRunnable); + mRecordManager.onRecordRemoved(mTaskId); } } @@ -316,7 +322,7 @@ public class StartingSurfaceDrawer { taskIds[i] = mStartingWindowRecords.keyAt(i); } for (int i = taskSize - 1; i >= 0; --i) { - removeWindow(taskIds[i], true); + removeWindow(taskIds[i]); } } @@ -335,9 +341,13 @@ public class StartingSurfaceDrawer { } } - void removeWindow(int taskId, boolean immediately) { + void removeWindow(int taskId) { mTmpRemovalInfo.taskId = taskId; - removeWindow(mTmpRemovalInfo, immediately); + removeWindow(mTmpRemovalInfo, true/* immediately */); + } + + void onRecordRemoved(int taskId) { + mStartingWindowRecords.remove(taskId); } StartingWindowRecord getRecord(int taskId) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java index 144547885501..fed2f34b5e0c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java @@ -92,7 +92,8 @@ class WindowlessSnapshotWindowCreator { final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface, taskDescription.getBackgroundColor(), snapshot.hasImeSurface(), - runningTaskInfo.topActivityType, removeExecutor); + runningTaskInfo.topActivityType, removeExecutor, + taskId, mStartingWindowRecordManager); mStartingWindowRecordManager.addRecord(taskId, record); info.notifyAddComplete(wlw.mChildSurface); } @@ -104,8 +105,9 @@ class WindowlessSnapshotWindowCreator { SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface, int bgColor, boolean hasImeSurface, int activityType, - ShellExecutor removeExecutor) { - super(activityType, removeExecutor); + ShellExecutor removeExecutor, int id, + StartingSurfaceDrawer.StartingWindowRecordManager recordManager) { + super(activityType, removeExecutor, id, recordManager); mViewHost = viewHost; mChildSurface = childSurface; mBGColor = bgColor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java index 5f54f58557d1..56c0d0e67cab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java @@ -38,8 +38,6 @@ public class ShellSharedConstants { public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks"; // See IBackAnimation.aidl public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation"; - // See IFloatingTasks.aidl - public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks"; // See IDesktopMode.aidl public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode"; // See IDragAndDrop.aidl diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java index 4faa92979733..0d77a2e4610c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; +import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; import android.view.SurfaceControl; @@ -69,8 +70,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private final Rect mTmpRect = new Rect(); private final Rect mTmpRootRect = new Rect(); private final int[] mTmpLocation = new int[2]; + private final Rect mBoundsOnScreen = new Rect(); private final TaskViewTaskController mTaskViewTaskController; private Region mObscuredTouchRegion; + private Insets mCaptionInsets; public TaskView(Context context, TaskViewTaskController taskViewTaskController) { super(context, null, 0, 0, true /* disableBackgroundLayer */); @@ -169,6 +172,25 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, } /** + * Sets a region of the task to inset to allow for a caption bar. Currently only top insets + * are supported. + * <p> + * This region will be factored in as an area of taskview that is not touchable activity + * content (i.e. you don't need to additionally set {@link #setObscuredTouchRect(Rect)} for + * the caption area). + * + * @param captionInsets the insets to apply to task view. + */ + public void setCaptionInsets(Insets captionInsets) { + mCaptionInsets = captionInsets; + if (captionInsets == null) { + // If captions are null we can set them now; otherwise they'll get set in + // onComputeInternalInsets. + mTaskViewTaskController.setCaptionInsets(null); + } + } + + /** * Call when view position or size has changed. Do not call when animating. */ public void onLocationChanged() { @@ -230,6 +252,15 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, getLocationInWindow(mTmpLocation); mTmpRect.set(mTmpLocation[0], mTmpLocation[1], mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight()); + if (mCaptionInsets != null) { + mTmpRect.inset(mCaptionInsets); + getBoundsOnScreen(mBoundsOnScreen); + mTaskViewTaskController.setCaptionInsets(new Rect( + mBoundsOnScreen.left, + mBoundsOnScreen.top, + mBoundsOnScreen.right + getWidth(), + mBoundsOnScreen.top + mCaptionInsets.top)); + } inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE); if (mObscuredTouchRegion != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index 163cf501734c..93d763608b5f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -33,6 +33,7 @@ import android.os.Binder; import android.util.CloseGuard; import android.util.Slog; import android.view.SurfaceControl; +import android.view.WindowInsets; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -53,12 +54,13 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { private static final String TAG = TaskViewTaskController.class.getSimpleName(); private final CloseGuard mGuard = new CloseGuard(); - + private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + /** Used to inset the activity content to allow space for a caption bar. */ + private final Binder mCaptionInsetsOwner = new Binder(); private final ShellTaskOrganizer mTaskOrganizer; private final Executor mShellExecutor; private final SyncTransactionQueue mSyncQueue; private final TaskViewTransitions mTaskViewTransitions; - private TaskViewBase mTaskViewBase; private final Context mContext; /** @@ -69,18 +71,20 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { * in this situation to allow us to notify listeners correctly if the task failed to open. */ private ActivityManager.RunningTaskInfo mPendingInfo; - /* Indicates that the task we attempted to launch in the task view failed to launch. */ - private boolean mTaskNotFound; + private TaskViewBase mTaskViewBase; protected ActivityManager.RunningTaskInfo mTaskInfo; private WindowContainerToken mTaskToken; private SurfaceControl mTaskLeash; - private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + /* Indicates that the task we attempted to launch in the task view failed to launch. */ + private boolean mTaskNotFound; private boolean mSurfaceCreated; private SurfaceControl mSurfaceControl; private boolean mIsInitialized; private boolean mNotifiedForInitialized; + private boolean mHideTaskWithSurface = true; private TaskView.Listener mListener; private Executor mListenerExecutor; + private Rect mCaptionInsets; public TaskViewTaskController(Context context, ShellTaskOrganizer organizer, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { @@ -89,12 +93,27 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { mShellExecutor = organizer.getExecutor(); mSyncQueue = syncQueue; mTaskViewTransitions = taskViewTransitions; - if (mTaskViewTransitions != null) { - mTaskViewTransitions.addTaskView(this); - } + mShellExecutor.execute(() -> { + if (mTaskViewTransitions != null) { + mTaskViewTransitions.addTaskView(this); + } + }); mGuard.open("release"); } + /** + * Specifies if the task should be hidden when the surface is destroyed. + * <p>This is {@code true} by default. + * + * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the + * surface is destroyed, {@code true} otherwise. + */ + public void setHideTaskWithSurface(boolean hideTaskWithSurface) { + // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks + // are moved to a window in SystemUI in auto. + mHideTaskWithSurface = hideTaskWithSurface; + } + SurfaceControl getSurfaceControl() { return mSurfaceControl; } @@ -220,10 +239,10 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } private void performRelease() { - if (mTaskViewTransitions != null) { - mTaskViewTransitions.removeTaskView(this); - } mShellExecutor.execute(() -> { + if (mTaskViewTransitions != null) { + mTaskViewTransitions.removeTaskView(this); + } mTaskOrganizer.removeListener(this); resetTaskInfo(); }); @@ -250,9 +269,17 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { mTaskNotFound = false; } + /** This method shouldn't be called when shell transitions are enabled. */ private void updateTaskVisibility() { + boolean visible = mSurfaceCreated; + if (!visible && !mHideTaskWithSurface) { + return; + } WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */); + wct.setHidden(mTaskToken, !visible /* hidden */); + if (!visible) { + wct.reorder(mTaskToken, false /* onTop */); + } mSyncQueue.queue(wct); if (mListener == null) { return; @@ -312,18 +339,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { // we know about -- so leave clean-up here even if shell transitions are enabled. if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; - if (mListener != null) { - final int taskId = taskInfo.taskId; - mListenerExecutor.execute(() -> { - mListener.onTaskRemovalStarted(taskId); - }); - } - mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false); + final SurfaceControl taskLeash = mTaskLeash; + handleAndNotifyTaskRemoval(mTaskInfo); // Unparent the task when this surface is destroyed - mTransaction.reparent(mTaskLeash, null).apply(); + mTransaction.reparent(taskLeash, null).apply(); resetTaskInfo(); - mTaskViewBase.onTaskVanished(taskInfo); } @Override @@ -411,9 +432,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { if (mTaskToken == null) { return; } - // Sync Transactions can't operate simultaneously with shell transition collection. + if (isUsingShellTransitions()) { - mTaskViewTransitions.setTaskBounds(this, boundsOnScreen); + mShellExecutor.execute(() -> { + // Sync Transactions can't operate simultaneously with shell transition collection. + mTaskViewTransitions.setTaskBounds(this, boundsOnScreen); + }); return; } @@ -431,9 +455,37 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)"); return; } + mShellExecutor.execute(() -> { + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.removeTask(mTaskToken); + mTaskViewTransitions.closeTaskView(wct, this); + }); + } + + /** + * Sets a region of the task to inset to allow for a caption bar. + * + * @param captionInsets the rect for the insets in screen coordinates. + */ + void setCaptionInsets(Rect captionInsets) { + if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) { + return; + } + mCaptionInsets = captionInsets; + applyCaptionInsetsIfNeeded(); + } + + void applyCaptionInsetsIfNeeded() { + if (mTaskToken == null) return; WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.removeTask(mTaskToken); - mTaskViewTransitions.closeTaskView(wct, this); + if (mCaptionInsets != null) { + wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0, + WindowInsets.Type.captionBar(), mCaptionInsets); + } else { + wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0, + WindowInsets.Type.captionBar()); + } + mTaskOrganizer.applyTransaction(wct); } /** Should be called when the client surface is destroyed. */ @@ -467,6 +519,20 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } } + /** Notifies listeners of a task being removed and stops intercepting back presses on it. */ + private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) { + if (taskInfo != null) { + if (mListener != null) { + final int taskId = taskInfo.taskId; + mListenerExecutor.execute(() -> { + mListener.onTaskRemovalStarted(taskId); + }); + } + mTaskViewBase.onTaskVanished(taskInfo); + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, false); + } + } + /** Returns the task info for the task in the TaskView. */ @Nullable public ActivityManager.RunningTaskInfo getTaskInfo() { @@ -492,18 +558,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { */ void cleanUpPendingTask() { if (mPendingInfo != null) { - if (mListener != null) { - final int taskId = mPendingInfo.taskId; - mListenerExecutor.execute(() -> { - mListener.onTaskRemovalStarted(taskId); - }); - } - mTaskViewBase.onTaskVanished(mPendingInfo); - mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mPendingInfo.token, false); + final ActivityManager.RunningTaskInfo pendingInfo = mPendingInfo; + handleAndNotifyTaskRemoval(pendingInfo); // Make sure the task is removed WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.removeTask(mPendingInfo.token); + wct.removeTask(pendingInfo.token); mTaskViewTransitions.closeTaskView(wct, this); } resetTaskInfo(); @@ -528,16 +588,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { * is used instead. */ void prepareCloseAnimation() { - if (mTaskToken != null) { - if (mListener != null) { - final int taskId = mTaskInfo.taskId; - mListenerExecutor.execute(() -> { - mListener.onTaskRemovalStarted(taskId); - }); - } - mTaskViewBase.onTaskVanished(mTaskInfo); - mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false); - } + handleAndNotifyTaskRemoval(mTaskInfo); resetTaskInfo(); } @@ -564,6 +615,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { mTaskViewTransitions.updateBoundsState(this, boundsOnScreen); mTaskViewTransitions.updateVisibilityState(this, true /* visible */); wct.setBounds(mTaskToken, boundsOnScreen); + applyCaptionInsetsIfNeeded(); } else { // The surface has already been destroyed before the task has appeared, // so go ahead and hide the task entirely diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index 5baf2e320227..e03f82526bdb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -165,19 +165,6 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { return null; } - /** - * Returns all the pending transitions for a given `taskView`. - * @param taskView the pending transition should be for this. - */ - ArrayList<PendingTransition> findAllPending(TaskViewTaskController taskView) { - ArrayList<PendingTransition> list = new ArrayList<>(); - for (int i = mPending.size() - 1; i >= 0; --i) { - if (mPending.get(i).mTaskView != taskView) continue; - list.add(mPending.get(i)); - } - return list; - } - private PendingTransition findPending(IBinder claimed) { for (int i = 0; i < mPending.size(); ++i) { if (mPending.get(i).mClaimed != claimed) continue; @@ -202,15 +189,10 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { if (taskView == null) return null; // Opening types should all be initiated by shell if (!TransitionUtil.isClosingType(request.getType())) return null; - PendingTransition pending = findPendingCloseTransition(taskView); - if (pending == null) { - pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */); - } - if (pending.mClaimed != null) { - throw new IllegalStateException("Task is closing in 2 collecting transitions?" - + " This state doesn't make sense"); - } + PendingTransition pending = new PendingTransition(request.getType(), null, + taskView, null /* cookie */); pending.mClaimed = transition; + mPending.add(pending); return new WindowContainerTransaction(); } @@ -278,10 +260,9 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { // Task view isn't visible, the bounds will next visibility update. return; } - PendingTransition pendingOpen = findPendingOpeningTransition(taskView); - if (pendingOpen != null) { - // There is already an opening transition in-flight, the window bounds will be - // set in prepareOpenAnimation (via the window crop) if needed. + if (hasPending()) { + // There is already a transition in-flight, the window bounds will be set in + // prepareOpenAnimation. return; } WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -409,7 +390,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { } // No animation, just show it immediately. startTransaction.apply(); - finishCallback.onTransitionFinished(wct, null /* wctCB */); + finishCallback.onTransitionFinished(wct); startNextTransition(); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index d0a361a8ecd2..00f6a1cc1167 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -19,10 +19,10 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; - import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; @@ -40,13 +40,13 @@ import android.view.WindowManager; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.split.SplitScreenUtils; +import com.android.wm.shell.desktopmode.DesktopModeStatus; +import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -70,6 +70,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private RecentsTransitionHandler mRecentsHandler; private StageCoordinator mSplitHandler; private final KeyguardTransitionHandler mKeyguardHandler; + private DesktopTasksController mDesktopTasksController; private UnfoldTransitionHandler mUnfoldHandler; private static class MixedTransition { @@ -87,8 +88,11 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, /** Keyguard exit/occlude/unocclude transition. */ static final int TYPE_KEYGUARD = 5; + /** Recents Transition while in desktop mode. */ + static final int TYPE_RECENTS_DURING_DESKTOP = 6; + /** Fuld/Unfold transition. */ - static final int TYPE_UNFOLD = 6; + static final int TYPE_UNFOLD = 7; /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -117,14 +121,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mTransition = transition; } - void joinFinishArgs(WindowContainerTransaction wct, - WindowContainerTransactionCallback wctCB) { - if (wctCB != null) { - // Technically can probably support 1, but don't want to encourage CB usage since - // it creates instabliity, so just throw. - throw new IllegalArgumentException("Can't mix transitions that require finish" - + " sync callback"); - } + void joinFinishArgs(WindowContainerTransaction wct) { if (wct != null) { if (mFinishWCT == null) { mFinishWCT = wct; @@ -139,17 +136,19 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player, Optional<SplitScreenController> splitScreenControllerOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional, + @Nullable PipTransitionController pipTransitionController, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, + Optional<DesktopTasksController> desktopTasksControllerOptional, Optional<UnfoldTransitionHandler> unfoldHandler) { mPlayer = player; mKeyguardHandler = keyguardHandler; - if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent() + if (Transitions.ENABLE_SHELL_TRANSITIONS + && pipTransitionController != null && splitScreenControllerOptional.isPresent()) { // Add after dependencies because it is higher priority shellInit.addInitCallback(() -> { - mPipHandler = pipTouchHandlerOptional.get().getTransitionHandler(); + mPipHandler = pipTransitionController; mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler(); mPlayer.addHandler(this); if (mSplitHandler != null) { @@ -159,6 +158,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mRecentsHandler != null) { mRecentsHandler.addMixer(this); } + mDesktopTasksController = desktopTasksControllerOptional.orElse(null); mUnfoldHandler = unfoldHandler.orElse(null); }, this); } @@ -239,8 +239,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @Override public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) { - if (mRecentsHandler != null && mSplitHandler.isSplitScreenVisible()) { - return this; + if (mRecentsHandler != null) { + if (mSplitHandler.isSplitScreenVisible()) { + return this; + } else if (mDesktopTasksController != null + // Check on the default display. Recents/gesture nav is only available there + && mDesktopTasksController.getVisibleTaskCount(DEFAULT_DISPLAY) > 0) { + return this; + } } return null; } @@ -254,6 +260,13 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); mixed.mLeftoversHandler = mRecentsHandler; mActiveTransitions.add(mixed); + } else if (DesktopModeStatus.isEnabled()) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + + "desktop mode is active, so treat it as Mixed."); + final MixedTransition mixed = new MixedTransition( + MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition); + mixed.mLeftoversHandler = mRecentsHandler; + mActiveTransitions.add(mixed); } else { throw new IllegalStateException("Accepted a recents transition but don't know how to" + " handle it"); @@ -340,6 +353,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) { return animateKeyguard(mixed, info, startTransaction, finishTransaction, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { + return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction, + finishCallback); } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback); } else { @@ -366,12 +382,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, info.getChanges().remove(i); } } - Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + Transitions.TransitionFinishCallback finishCB = (wct) -> { --mixed.mInFlightSubAnimations; - mixed.joinFinishArgs(wct, wctCB); + mixed.joinFinishArgs(wct); if (mixed.mInFlightSubAnimations > 0) return; mActiveTransitions.remove(mixed); - finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB); + finishCallback.onTransitionFinished(mixed.mFinishWCT); }; if (pipChange == null) { if (mixed.mLeftoversHandler != null) { @@ -438,15 +454,15 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return false; } final boolean isGoingHome = homeIsOpening; - Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + Transitions.TransitionFinishCallback finishCB = (wct) -> { --mixed.mInFlightSubAnimations; - mixed.joinFinishArgs(wct, wctCB); + mixed.joinFinishArgs(wct); if (mixed.mInFlightSubAnimations > 0) return; mActiveTransitions.remove(mixed); if (isGoingHome) { mSplitHandler.onTransitionAnimationComplete(); } - finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB); + finishCallback.onTransitionFinished(mixed.mFinishWCT); }; if (isGoingHome || mSplitHandler.getSplitItemPosition(pipChange.getLastParent()) != SPLIT_POSITION_UNDEFINED) { @@ -563,12 +579,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // We need to split the transition into 2 parts: the split part and the display part. mixed.mInFlightSubAnimations = 2; - Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + Transitions.TransitionFinishCallback finishCB = (wct) -> { --mixed.mInFlightSubAnimations; - mixed.joinFinishArgs(wct, wctCB); + mixed.joinFinishArgs(wct); if (mixed.mInFlightSubAnimations > 0) return; mActiveTransitions.remove(mixed); - finishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */); + finishCallback.onTransitionFinished(mixed.mFinishWCT); }; // Dispatch the display change. This will most-likely be taken by the default handler. @@ -591,7 +607,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @NonNull Transitions.TransitionFinishCallback finishCallback) { // Split-screen is only interested in the recents transition finishing (and merging), so // just wrap finish and start recents animation directly. - Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + Transitions.TransitionFinishCallback finishCB = (wct) -> { mixed.mInFlightSubAnimations = 0; mActiveTransitions.remove(mixed); // If pair-to-pair switching, the post-recents clean-up isn't needed. @@ -603,7 +619,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mSplitHandler.onRecentsPairToPairAnimationFinish(wct); } mSplitHandler.onTransitionAnimationComplete(); - finishCallback.onTransitionFinished(wct, wctCB); + finishCallback.onTransitionFinished(wct); }; mixed.mInFlightSubAnimations = 1; mSplitHandler.onRecentsInSplitAnimationStart(info); @@ -621,11 +637,11 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + final Transitions.TransitionFinishCallback finishCB = (wct) -> { mixed.mInFlightSubAnimations--; if (mixed.mInFlightSubAnimations == 0) { mActiveTransitions.remove(mixed); - finishCallback.onTransitionFinished(wct, wctCB); + finishCallback.onTransitionFinished(wct); } }; mixed.mInFlightSubAnimations++; @@ -641,22 +657,43 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return true; } + private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + boolean consumed = mRecentsHandler.startAnimation( + mixed.mTransition, info, startTransaction, finishTransaction, finishCallback); + if (!consumed) { + return false; + } + if (mDesktopTasksController != null) { + mDesktopTasksController.syncSurfaceState(info, finishTransaction); + return true; + } + + return false; + } + private boolean animateUnfold(@NonNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + final Transitions.TransitionFinishCallback finishCB = (wct) -> { mixed.mInFlightSubAnimations--; if (mixed.mInFlightSubAnimations > 0) return; mActiveTransitions.remove(mixed); - finishCallback.onTransitionFinished(wct, wctCB); + finishCallback.onTransitionFinished(wct); }; mixed.mInFlightSubAnimations = 1; // Sync pip state. if (mPipHandler != null) { mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); } + if (mSplitHandler != null && mSplitHandler.isSplitActive()) { + mSplitHandler.updateSurfaces(startTransaction); + } return mUnfoldHandler.startAnimation( mixed.mTransition, info, startTransaction, finishTransaction, finishCB); } @@ -727,6 +764,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, finishCallback); } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) { mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { + mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, + finishCallback); } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } else { @@ -754,6 +794,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) { mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); + } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { + mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); } 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 e52fd00e7df7..7df658e6c9db 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 @@ -300,7 +300,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // immediately finishes since there is no animation for screen-wake. if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) { startTransaction.apply(); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); return true; } @@ -309,7 +309,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { || (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) { startTransaction.apply(); finishTransaction.apply(); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); return true; } @@ -323,7 +323,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final Runnable onAnimFinish = () -> { if (!animations.isEmpty()) return; mAnimations.remove(transition); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); }; final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks = @@ -407,7 +407,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); } // Rotation change of independent non display window container. - if (change.getParent() == null + if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY) && change.getStartRotation() != change.getEndRotation()) { startRotationAnimation(startTransaction, change, info, ROTATION_ANIMATION_ROTATE, animations, onAnimFinish); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index 4e3d220f1ea2..fab2dd2bf3e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -68,7 +68,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { final IBinder.DeathRecipient remoteDied = () -> { Log.e(Transitions.TAG, "Remote transition died, finishing"); mMainExecutor.execute( - () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); + () -> finishCallback.onTransitionFinished(null /* wct */)); }; IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { @Override @@ -81,7 +81,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { finishTransaction.merge(sct); } mMainExecutor.execute(() -> { - finishCallback.onTransitionFinished(wct, null /* wctCB */); + finishCallback.onTransitionFinished(wct); }); } }; @@ -104,7 +104,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { if (mRemote.asBinder() != null) { mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); } - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); } return true; } @@ -122,8 +122,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { // remote applied the transaction, but applying twice will break surfaceflinger // so just assume the worst-case and clear the local transaction. t.clear(); - mMainExecutor.execute( - () -> finishCallback.onTransitionFinished(wct, null /* wctCB */)); + mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct)); } }; try { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index a242c72db8b3..a90edf20f94e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -133,11 +133,10 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } mMainExecutor.execute(() -> { mRequestedRemotes.remove(transition); - finishCallback.onTransitionFinished(wct, null /* wctCB */); + finishCallback.onTransitionFinished(wct); }); } }; - Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread()); try { // If the remote is actually in the same process, then make a copy of parameters since // remote impls assume that they have to clean-up native references. @@ -149,12 +148,12 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb); // assume that remote will apply the start transaction. startTransaction.clear(); + Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread()); } catch (RemoteException e) { Log.e(Transitions.TAG, "Error running remote transition.", e); unhandleDeath(remote.asBinder(), finishCallback); mRequestedRemotes.remove(transition); - mMainExecutor.execute( - () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); + mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */)); } return true; } @@ -186,9 +185,12 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback) { final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget); - final IRemoteTransition remote = remoteTransition.getRemoteTransition(); + if (remoteTransition == null) return; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Merge into remote: %s", remoteTransition); + + final IRemoteTransition remote = remoteTransition.getRemoteTransition(); if (remote == null) return; IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { @@ -207,7 +209,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { + "that the mergeTarget's RemoteTransition impl erroneously " + "accepted/ran the merge request after finishing the mergeTarget"); } - finishCallback.onTransitionFinished(wct, null /* wctCB */); + finishCallback.onTransitionFinished(wct); }); } }; @@ -313,8 +315,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } } for (int i = mPendingFinishCallbacks.size() - 1; i >= 0; --i) { - mPendingFinishCallbacks.get(i).onTransitionFinished( - null /* wct */, null /* wctCB */); + mPendingFinishCallbacks.get(i).onTransitionFinished(null /* wct */); } mPendingFinishCallbacks.clear(); }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java index d2795959494a..87c438a5b37d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java @@ -43,7 +43,7 @@ class SleepHandler implements Transitions.TransitionHandler { @NonNull Transitions.TransitionFinishCallback finishCallback) { mSleepTransitions.remove(transition); startTransaction.apply(); - finishCallback.onTransitionFinished(null, null); + finishCallback.onTransitionFinished(null); return true; } 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 d978eafa97f3..d07d2b7b6db9 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 @@ -346,7 +346,7 @@ public class TransitionAnimationHelper { .setFrameScale(1) .setPixelFormat(PixelFormat.RGBA_8888) .setChildrenOnly(true) - .setAllowProtected(true) + .setAllowProtected(false) .setCaptureSecureLayers(true) .build(); final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer = 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 2327d86ab618..c74b3f30e52d 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 @@ -21,12 +21,14 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING; import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; +import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; @@ -61,7 +63,6 @@ import android.window.TransitionInfo; import android.window.TransitionMetrics; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import android.window.WindowOrganizer; import androidx.annotation.BinderThread; @@ -148,19 +149,28 @@ public class Transitions implements RemoteCallable<Transitions>, /** Transition type for maximize to freeform transition. */ public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9; - /** Transition type to freeform in desktop mode. */ - public static final int TRANSIT_ENTER_FREEFORM = WindowManager.TRANSIT_FIRST_CUSTOM + 10; + /** Transition type for starting the move to desktop mode. */ + public static final int TRANSIT_START_DRAG_TO_DESKTOP_MODE = + WindowManager.TRANSIT_FIRST_CUSTOM + 10; - /** Transition type to freeform in desktop mode. */ - public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11; + /** Transition type for finalizing the move to desktop mode. */ + public static final int TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE = + WindowManager.TRANSIT_FIRST_CUSTOM + 11; /** Transition type to fullscreen from desktop mode. */ public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12; /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */ - public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE = + public static final int TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 13; + /** Transition type to animate the toggle resize between the max and default desktop sizes. */ + public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE = + WindowManager.TRANSIT_FIRST_CUSTOM + 14; + + /** Transition to animate task to desktop. */ + public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15; + private final WindowOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; @@ -724,11 +734,15 @@ public class Transitions implements RemoteCallable<Transitions>, final int changeSize = info.getChanges().size(); boolean taskChange = false; boolean transferStartingWindow = false; + int noAnimationBehindStartingWindow = 0; boolean allOccluded = changeSize > 0; for (int i = changeSize - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); taskChange |= change.getTaskInfo() != null; transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT); + if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) { + noAnimationBehindStartingWindow++; + } if (!change.hasFlags(FLAG_IS_OCCLUDED)) { allOccluded = false; } @@ -736,9 +750,11 @@ public class Transitions implements RemoteCallable<Transitions>, // There does not need animation when: // A. Transfer starting window. Apply transfer starting window directly if there is no other // task change. Since this is an activity->activity situation, we can detect it by selecting - // transitions with only 2 changes where neither are tasks and one is a starting-window - // recipient. - if (!taskChange && transferStartingWindow && changeSize == 2 + // transitions with only 2 changes where + // 1. neither are tasks, and + // 2. one is a starting-window recipient, or all change is behind starting window. + if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize) + && changeSize == 2 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all // changes are underneath another change. || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT) @@ -813,7 +829,7 @@ public class Transitions implements RemoteCallable<Transitions>, ready.mStartT.apply(); } // finish now since there's nothing to animate. Calls back into processReadyQueue - onFinish(ready, null, null); + onFinish(ready, null); return; } playTransition(ready); @@ -833,7 +849,7 @@ public class Transitions implements RemoteCallable<Transitions>, + " in case they can be merged", ready, playing); mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId()); playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT, - playing.mToken, (wct, cb) -> onMerged(playing, ready)); + playing.mToken, (wct) -> onMerged(playing, ready)); } private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) { @@ -883,7 +899,7 @@ public class Transitions implements RemoteCallable<Transitions>, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s", active.mHandler); boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo, - active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb)); + active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct)); if (consumed) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler"); mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler); @@ -892,7 +908,7 @@ public class Transitions implements RemoteCallable<Transitions>, } // Otherwise give every other handler a chance active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT, - active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler); + active.mFinishT, (wct) -> onFinish(active, wct), active.mHandler); } /** @@ -969,8 +985,7 @@ public class Transitions implements RemoteCallable<Transitions>, } private void onFinish(ActiveTransition active, - @Nullable WindowContainerTransaction wct, - @Nullable WindowContainerTransactionCallback wctCB) { + @Nullable WindowContainerTransaction wct) { final Track track = mTracks.get(active.getTrack()); if (track.mActiveTransition != active) { Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or " @@ -1019,11 +1034,11 @@ public class Transitions implements RemoteCallable<Transitions>, // Now perform all the finish callbacks (starting with the playing one and then all the // transitions merged into it). releaseSurfaces(active.mInfo); - mOrganizer.finishTransition(active.mToken, wct, wctCB); + mOrganizer.finishTransition(active.mToken, wct); if (active.mMerged != null) { for (int iM = 0; iM < active.mMerged.size(); ++iM) { ActiveTransition merged = active.mMerged.get(iM); - mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */); + mOrganizer.finishTransition(merged.mToken, null /* wct */); releaseSurfaces(merged.mInfo); } active.mMerged.clear(); @@ -1090,7 +1105,9 @@ public class Transitions implements RemoteCallable<Transitions>, } } } - if (request.getType() == TRANSIT_KEYGUARD_OCCLUDE && request.getTriggerTask() != null + final boolean isOccludingKeyguard = request.getType() == TRANSIT_KEYGUARD_OCCLUDE + || ((request.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0); + if (isOccludingKeyguard && request.getTriggerTask() != null && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FREEFORM) { // This freeform task is on top of keyguard, so its windowing mode should be changed to // fullscreen. @@ -1162,7 +1179,7 @@ public class Transitions implements RemoteCallable<Transitions>, forceFinish.mHandler.onTransitionConsumed( forceFinish.mToken, true /* aborted */, null /* finishTransaction */); } - onFinish(forceFinish, null, null); + onFinish(forceFinish, null); } } if (track.isIdle() || mReadyDuringSync.isEmpty()) { @@ -1182,7 +1199,7 @@ public class Transitions implements RemoteCallable<Transitions>, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s" + " into %s via a SLEEP proxy", nextSync, playing); playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT, - playing.mToken, (wct, cb) -> {}); + playing.mToken, (wct) -> {}); // it's possible to complete immediately. If that happens, just repeat the signal // loop until we either finish everything or start playing an animation that isn't // finishing immediately. @@ -1210,11 +1227,8 @@ public class Transitions implements RemoteCallable<Transitions>, * The transition must not touch the surfaces after this has been called. * * @param wct A WindowContainerTransaction to run along with the transition clean-up. - * @param wctCB A sync callback that will be run when the transition clean-up is done and - * wct has been applied. */ - void onTransitionFinished(@Nullable WindowContainerTransaction wct, - @Nullable WindowContainerTransactionCallback wctCB); + void onTransitionFinished(@Nullable WindowContainerTransaction wct); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java index 367676f54aba..f7a060f0638b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java @@ -46,5 +46,7 @@ public interface ShellUnfoldProgressProvider { default void onStateChangeProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {} default void onStateChangeFinished() {} + + default void onFoldStateChanged(boolean isFolded) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index f148412205bf..68b5a81f8d7b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -63,6 +63,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Nullable private IBinder mTransition; + private boolean mAnimationFinished = false; private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>(); public UnfoldTransitionHandler(ShellInit shellInit, @@ -132,6 +133,13 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene startTransaction.apply(); mFinishCallback = finishCallback; + + // Shell transition started when unfold animation has already finished, + // finish shell transition immediately + if (mAnimationFinished) { + finishTransitionIfNeeded(); + } + return true; } @@ -161,17 +169,8 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public void onStateChangeFinished() { - if (mFinishCallback == null) return; - - for (int i = 0; i < mAnimators.size(); i++) { - final UnfoldTaskAnimator animator = mAnimators.get(i); - animator.clearTasks(); - animator.stop(); - } - - mFinishCallback.onTransitionFinished(null, null); - mFinishCallback = null; - mTransition = null; + mAnimationFinished = true; + finishTransitionIfNeeded(); } @Override @@ -193,7 +192,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene } // Apply changes happening during the unfold animation immediately t.apply(); - finishCallback.onTransitionFinished(null, null); + finishCallback.onTransitionFinished(null); } } @@ -218,4 +217,25 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene public boolean willHandleTransition() { return mTransition != null; } + + @Override + public void onFoldStateChanged(boolean isFolded) { + if (isFolded) { + mAnimationFinished = false; + } + } + + private void finishTransitionIfNeeded() { + if (mFinishCallback == null) return; + + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + animator.clearTasks(); + animator.stop(); + } + + mFinishCallback.onTransitionFinished(null); + mFinishCallback = null; + mTransition = null; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java index f81fc6fbea49..6bba0d1386fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java @@ -110,7 +110,7 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, for (int i = state.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = state.sourceAt(i); if (source.getType() == WindowInsets.Type.navigationBars() - && source.insetsRoundedCornerFrame()) { + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { return source; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java index a4cf149cc3b5..bb5d54652460 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java @@ -50,11 +50,11 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; +import dagger.Lazy; + import java.util.Optional; import java.util.concurrent.Executor; -import dagger.Lazy; - /** * This helper class contains logic that calculates scaling and cropping parameters * for the folding/unfolding animation. As an input it receives TaskInfo objects and @@ -149,7 +149,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, for (int i = state.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = state.sourceAt(i); if (source.getType() == WindowInsets.Type.navigationBars() - && source.insetsRoundedCornerFrame()) { + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { return source; } } @@ -270,7 +270,6 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, @Override public void prepareStartTransaction(Transaction transaction) { mUnfoldBackgroundController.ensureBackground(transaction); - mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java index f209521b1da4..0edcff45f648 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java @@ -26,6 +26,8 @@ import java.util.Objects; * tasks/leashes/etc in Launcher */ public class SplitBounds implements Parcelable { + public static final String KEY_EXTRA_SPLIT_BOUNDS = "key_SplitBounds"; + public final Rect leftTopBounds; public final Rect rightBottomBounds; /** This rect represents the actual gap between the two apps */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 39fb7936747e..cf1692018518 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -33,11 +33,14 @@ import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.annotation.Nullable; + import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; /** @@ -89,6 +92,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } @Override + public void setSplitScreenController(SplitScreenController splitScreenController) {} + + @Override public boolean onTaskOpening( RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -187,7 +193,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { final DragPositioningCallback dragPositioningCallback = new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController, - null /* disallowedAreaForEndBounds */); + 0 /* disallowedAreaForEndBoundsHeight */); final CaptionTouchEventListener touchEventListener = new CaptionTouchEventListener(taskInfo, dragPositioningCallback); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); @@ -254,7 +260,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { * @return {@code true} if a drag is happening; or {@code false} if it is not */ @Override - public boolean handleMotionEvent(MotionEvent e) { + public boolean handleMotionEvent(@Nullable View v, MotionEvent e) { final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { return false; @@ -268,7 +274,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { return false; } case MotionEvent.ACTION_MOVE: { - int dragPointerIdx = e.findPointerIndex(mDragPointerId); + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } + final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mIsDragging = true; @@ -276,7 +285,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { - int dragPointerIdx = e.findPointerIndex(mDragPointerId); + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } + final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); final boolean wasDragging = mIsDragging; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 116af7094e13..c18973132364 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -64,7 +64,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL Handler handler, Choreographer choreographer, SyncTransactionQueue syncQueue) { - super(context, displayController, taskOrganizer, taskInfo, taskSurface); + super(context, displayController, taskOrganizer, taskInfo, taskSurface, + taskInfo.getConfiguration()); mHandler = handler; mChoreographer = choreographer; @@ -113,7 +114,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = R.layout.caption_window_decor; - mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; + mRelayoutParams.mCaptionHeightId = getCaptionHeightId(); mRelayoutParams.mShadowRadiusId = shadowRadiusID; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; @@ -143,8 +144,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mHandler, mChoreographer, mDisplay.getDisplayId(), + 0 /* taskCornerRadius */, mDecorationContainerSurface, - mDragPositioningCallback); + mDragPositioningCallback, + mSurfaceControlBuilderSupplier, + mSurfaceControlTransactionSupplier); } final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) @@ -221,4 +225,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL closeDragResizeListener(); super.close(); } + + @Override + int getCaptionHeightId() { + return R.dimen.freeform_decor_caption_height; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 9fd57d7e1201..b4e181808194 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -19,12 +19,13 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; - +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 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 static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE; import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE; import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION; +import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -34,6 +35,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; @@ -42,6 +44,7 @@ import android.os.IBinder; import android.os.Looper; import android.util.SparseArray; import android.view.Choreographer; +import android.view.GestureDetector; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; @@ -53,23 +56,30 @@ import android.view.View; import android.view.WindowManager; import android.window.TransitionInfo; import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.desktopmode.DesktopModeController; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; +import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.sysui.KeyguardChangeListener; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; -import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.TaskCornersListener; +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener; import java.util.Optional; import java.util.function.Supplier; @@ -85,18 +95,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory; private final ActivityTaskManager mActivityTaskManager; private final ShellTaskOrganizer mTaskOrganizer; + private final ShellController mShellController; private final Context mContext; private final Handler mMainHandler; private final Choreographer mMainChoreographer; private final DisplayController mDisplayController; private final SyncTransactionQueue mSyncQueue; - private final Optional<DesktopModeController> mDesktopModeController; private final Optional<DesktopTasksController> mDesktopTasksController; private boolean mTransitionDragActive; private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>(); - private final TaskCornersListener mCornersListener = new TaskCornersListenerImpl(); + private final ExclusionRegionListener mExclusionRegionListener = + new ExclusionRegionListenerImpl(); private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); @@ -106,37 +117,42 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final Supplier<SurfaceControl.Transaction> mTransactionFactory; private final Transitions mTransitions; - private Optional<SplitScreenController> mSplitScreenController; + private SplitScreenController mSplitScreenController; - private ValueAnimator mDragToDesktopValueAnimator; + private MoveToDesktopAnimator mMoveToDesktopAnimator; private final Rect mDragToDesktopAnimationStartBounds = new Rect(); - private boolean mDragToDesktopAnimationStarted; + private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener; + private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; public DesktopModeWindowDecorViewModel( Context context, Handler mainHandler, Choreographer mainChoreographer, + ShellInit shellInit, ShellTaskOrganizer taskOrganizer, DisplayController displayController, + ShellController shellController, SyncTransactionQueue syncQueue, Transitions transitions, - Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController, - Optional<SplitScreenController> splitScreenController) { + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer + ) { this( context, mainHandler, mainChoreographer, + shellInit, taskOrganizer, displayController, + shellController, syncQueue, transitions, - desktopModeController, desktopTasksController, - splitScreenController, new DesktopModeWindowDecoration.Factory(), new InputMonitorFactory(), - SurfaceControl.Transaction::new); + SurfaceControl.Transaction::new, + new DesktopModeKeyguardChangeListener(), + rootTaskDisplayAreaOrganizer); } @VisibleForTesting @@ -144,31 +160,40 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { Context context, Handler mainHandler, Choreographer mainChoreographer, + ShellInit shellInit, ShellTaskOrganizer taskOrganizer, DisplayController displayController, + ShellController shellController, SyncTransactionQueue syncQueue, Transitions transitions, - Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController, - Optional<SplitScreenController> splitScreenController, DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory, - Supplier<SurfaceControl.Transaction> transactionFactory) { + Supplier<SurfaceControl.Transaction> transactionFactory, + DesktopModeKeyguardChangeListener desktopModeKeyguardChangeListener, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { mContext = context; mMainHandler = mainHandler; mMainChoreographer = mainChoreographer; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mTaskOrganizer = taskOrganizer; + mShellController = shellController; mDisplayController = displayController; - mSplitScreenController = splitScreenController; mSyncQueue = syncQueue; mTransitions = transitions; - mDesktopModeController = desktopModeController; mDesktopTasksController = desktopTasksController; mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory; mInputMonitorFactory = inputMonitorFactory; mTransactionFactory = transactionFactory; + mDesktopModeKeyguardChangeListener = desktopModeKeyguardChangeListener; + mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; + + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener); } @Override @@ -177,6 +202,23 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } @Override + public void setSplitScreenController(SplitScreenController splitScreenController) { + mSplitScreenController = splitScreenController; + mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() { + @Override + public void onTaskStageChanged(int taskId, int stage, boolean visible) { + if (visible) { + DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId); + if (decor != null && DesktopModeStatus.isEnabled() + && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo)); + } + } + } + }); + } + + @Override public boolean onTaskOpening( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -193,7 +235,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change) { if (change.getMode() == WindowManager.TRANSIT_CHANGE - && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE)) { + && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE + || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE + || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE + || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE + || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) { mWindowDecorByTaskId.get(change.getTaskInfo().taskId) .addTransitionPausingRelayout(transition); } @@ -225,7 +271,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { removeTaskFromEventReceiver(oldTaskInfo.displayId); incrementEventReceiverTasks(taskInfo.displayId); } - decoration.relayout(taskInfo); } @@ -236,7 +281,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); - if (!shouldShowWindowDecor(taskInfo)) { if (decoration != null) { destroyWindowDecoration(taskInfo); @@ -275,15 +319,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } - private class DesktopModeTouchEventListener implements - View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { + private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener + implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener, + DragDetector.MotionEventHandler{ private final int mTaskId; private final WindowContainerToken mTaskToken; private final DragPositioningCallback mDragPositioningCallback; private final DragDetector mDragDetector; + private final GestureDetector mGestureDetector; private boolean mIsDragging; + private boolean mShouldClick; private int mDragPointerId = -1; private DesktopModeTouchEventListener( @@ -293,6 +340,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTaskToken = taskInfo.token; mDragPositioningCallback = dragPositioningCallback; mDragDetector = new DragDetector(this); + mGestureDetector = new GestureDetector(mContext, this); } @Override @@ -301,18 +349,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int id = v.getId(); if (id == R.id.close_window || id == R.id.close_button) { mTaskOperations.closeTask(mTaskToken); - if (mSplitScreenController.isPresent() - && mSplitScreenController.get().isSplitScreenVisible()) { - int remainingTaskPosition = mTaskId == mSplitScreenController.get() - .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId - ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT; - ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get() - .getTaskInfo(remainingTaskPosition); - mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId); + if (isTaskInSplitScreen(mTaskId)) { + RunningTaskInfo remainingTask = getOtherSplitTask(mTaskId); + mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId); } + decoration.closeMaximizeMenu(); } else if (id == R.id.back_button) { mTaskOperations.injectBackKey(); } else if (id == R.id.caption_handle || id == R.id.open_menu_button) { + decoration.closeMaximizeMenu(); if (!decoration.isHandleMenuActive()) { moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId)); decoration.createHandleMenu(); @@ -320,13 +365,24 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { decoration.closeHandleMenu(); } } else if (id == R.id.desktop_button) { - mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); - mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId)); + if (mDesktopTasksController.isPresent()) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + // App sometimes draws before the insets from WindowDecoration#relayout have + // been added, so they must be added here + mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct); + decoration.incrementRelayoutBlock(); + mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct); + closeOtherSplitTask(mTaskId); + } decoration.closeHandleMenu(); } else if (id == R.id.fullscreen_button) { - mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false)); mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId)); decoration.closeHandleMenu(); + } else if (id == R.id.split_screen_button) { + decoration.closeHandleMenu(); + mDesktopTasksController.ifPresent(c -> { + c.requestSplit(decoration.mTaskInfo); + }); } else if (id == R.id.collapse_menu_button) { decoration.closeHandleMenu(); } else if (id == R.id.select_button) { @@ -334,8 +390,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // TODO(b/278084491): dev option to enable display switching // remove when select is implemented mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId)); - decoration.closeHandleMenu(); } + } else if (id == R.id.maximize_window) { + moveTaskToFront(decoration.mTaskInfo); + if (decoration.isMaximizeMenuActive()) { + decoration.closeMaximizeMenu(); + return; + } + final RunningTaskInfo taskInfo = decoration.mTaskInfo; + mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize( + taskInfo, decoration)); + decoration.closeHandleMenu(); + } else if (id == R.id.maximize_menu_maximize_button) { + final RunningTaskInfo taskInfo = decoration.mTaskInfo; + mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize( + taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId))); + decoration.closeHandleMenu(); + decoration.closeMaximizeMenu(); + } else if (id == R.id.maximize_menu_snap_left_button) { + final RunningTaskInfo taskInfo = decoration.mTaskInfo; + mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen( + taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.LEFT)); + decoration.closeHandleMenu(); + decoration.closeMaximizeMenu(); + } else if (id == R.id.maximize_menu_snap_right_button) { + final RunningTaskInfo taskInfo = decoration.mTaskInfo; + mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen( + taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.RIGHT)); + decoration.closeHandleMenu(); + decoration.closeMaximizeMenu(); } } @@ -347,13 +430,29 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return false; } moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId)); - return mDragDetector.onMotionEvent(e); + return mDragDetector.onMotionEvent(v, e); + } + + @Override + public boolean onLongClick(View v) { + final int id = v.getId(); + if (id == R.id.maximize_window) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + moveTaskToFront(decoration.mTaskInfo); + if (decoration.isMaximizeMenuActive()) { + decoration.closeMaximizeMenu(); + } else { + decoration.closeHandleMenu(); + decoration.createMaximizeMenu(); + } + return true; + } + return false; } private void moveTaskToFront(RunningTaskInfo taskInfo) { if (!taskInfo.isFocused) { mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo)); - mDesktopModeController.ifPresent(c -> c.moveTaskToFront(taskInfo)); } } @@ -362,16 +461,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { * @return {@code true} if the motion event is handled. */ @Override - public boolean handleMotionEvent(MotionEvent e) { + public boolean handleMotionEvent(@Nullable View v, MotionEvent e) { final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); - if (DesktopModeStatus.isProto2Enabled() + if (DesktopModeStatus.isEnabled() && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { return false; } - if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent() - && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId) - == WINDOWING_MODE_FULLSCREEN) { - return false; + if (mGestureDetector.onTouchEvent(e)) { + return true; } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { @@ -380,21 +477,40 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); mIsDragging = false; - return false; + mShouldClick = true; + return true; } case MotionEvent.ACTION_MOVE: { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } final int dragPointerIdx = e.findPointerIndex(mDragPointerId); - mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo, - decoration.mTaskSurface, e.getRawY(dragPointerIdx))); - mDragPositioningCallback.onDragPositioningMove( + final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); + mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo, + decoration.mTaskSurface, + new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)), + newTaskBounds)); mIsDragging = true; + mShouldClick = false; return true; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { + final boolean wasDragging = mIsDragging; + if (!wasDragging) { + if (mShouldClick && v != null) { + v.performClick(); + mShouldClick = false; + return true; + } + return false; + } + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } final int dragPointerIdx = e.findPointerIndex(mDragPointerId); // Position of the task is calculated by subtracting the raw location of the // motion event (the location of the motion relative to the display) by the @@ -402,17 +518,27 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final Point position = new Point( (int) (e.getRawX(dragPointerIdx) - e.getX(dragPointerIdx)), (int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx))); - mDragPositioningCallback.onDragPositioningEnd( + final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo, - position)); - final boolean wasDragging = mIsDragging; + position, + new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)), + newTaskBounds, mWindowDecorByTaskId.get(mTaskId))); mIsDragging = false; - return wasDragging; + return true; } } return true; } + + @Override + public boolean onDoubleTap(@NonNull MotionEvent e) { + final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + mDesktopTasksController.ifPresent(c -> { + c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)); + }); + return true; + } } // InputEventReceiver to listen for touch input outside of caption bounds @@ -490,7 +616,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { */ private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev); - if (DesktopModeStatus.isProto2Enabled()) { + if (DesktopModeStatus.isEnabled()) { if (relevantDecor == null || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM || mTransitionDragActive) { @@ -499,14 +625,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } handleEventOutsideFocusedCaption(ev, relevantDecor); // Prevent status bar from reacting to a caption drag. - if (DesktopModeStatus.isProto2Enabled()) { + if (DesktopModeStatus.isEnabled()) { if (mTransitionDragActive) { inputMonitor.pilferPointers(); } - } else if (DesktopModeStatus.isProto1Enabled()) { - if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) { - inputMonitor.pilferPointers(); - } } } @@ -539,10 +661,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragToDesktopAnimationStartBounds.set( relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds()); boolean dragFromStatusBarAllowed = false; - if (DesktopModeStatus.isProto2Enabled()) { - // In proto2 any full screen task can be dragged to freeform - dragFromStatusBarAllowed = relevantDecor.mTaskInfo.getWindowingMode() - == WINDOWING_MODE_FULLSCREEN; + if (DesktopModeStatus.isEnabled()) { + // In proto2 any full screen or multi-window task can be dragged to + // freeform. + final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode(); + dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN + || windowingMode == WINDOWING_MODE_MULTI_WINDOW; } if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) { @@ -553,7 +677,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } case MotionEvent.ACTION_UP: { if (relevantDecor == null) { - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; mTransitionDragActive = false; return; } @@ -562,19 +686,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int statusBarHeight = getStatusBarHeight( relevantDecor.mTaskInfo.displayId); if (ev.getY() > 2 * statusBarHeight) { - if (DesktopModeStatus.isProto2Enabled()) { + if (DesktopModeStatus.isEnabled()) { animateToDesktop(relevantDecor, ev); - } else if (DesktopModeStatus.isProto1Enabled()) { - mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); } - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; return; - } else if (mDragToDesktopAnimationStarted) { - Point position = new Point((int) ev.getX(), (int) ev.getY()); + } else if (mMoveToDesktopAnimator != null) { + relevantDecor.incrementRelayoutBlock(); mDesktopTasksController.ifPresent( - c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, - position)); - mDragToDesktopAnimationStarted = false; + c -> c.cancelMoveToDesktop(relevantDecor.mTaskInfo, + mMoveToDesktopAnimator)); + mMoveToDesktopAnimator = null; return; } } @@ -594,21 +716,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int statusBarHeight = getStatusBarHeight( relevantDecor.mTaskInfo.displayId); if (ev.getY() > statusBarHeight) { - if (!mDragToDesktopAnimationStarted) { - mDragToDesktopAnimationStarted = true; + if (mMoveToDesktopAnimator == null) { + closeOtherSplitTask(relevantDecor.mTaskInfo.taskId); + mMoveToDesktopAnimator = new MoveToDesktopAnimator( + mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, + relevantDecor.mTaskSurface); mDesktopTasksController.ifPresent( - c -> c.moveToFreeform(relevantDecor.mTaskInfo, - mDragToDesktopAnimationStartBounds)); - startAnimation(relevantDecor); + c -> c.startMoveToDesktop(relevantDecor.mTaskInfo, + mDragToDesktopAnimationStartBounds, + mMoveToDesktopAnimator)); + mMoveToDesktopAnimator.startAnimation(); } } - if (mDragToDesktopAnimationStarted) { - Transaction t = mTransactionFactory.get(); - float width = (float) mDragToDesktopValueAnimator.getAnimatedValue() - * mDragToDesktopAnimationStartBounds.width(); - float x = ev.getX() - (width / 2); - t.setPosition(relevantDecor.mTaskSurface, x, ev.getY()); - t.apply(); + if (mMoveToDesktopAnimator != null) { + mMoveToDesktopAnimator.updatePosition(ev); } } break; @@ -616,7 +737,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { case MotionEvent.ACTION_CANCEL: { mTransitionDragActive = false; - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; } } } @@ -683,26 +804,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { animator.start(); } - private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) { - mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE); - mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION); - final Transaction t = mTransactionFactory.get(); - mDragToDesktopValueAnimator.addUpdateListener(animation -> { - final float animatorValue = (float) animation.getAnimatedValue(); - SurfaceControl sc = focusedDecor.mTaskSurface; - t.setScale(sc, animatorValue, animatorValue); - t.apply(); - }); - - mDragToDesktopValueAnimator.start(); - } - @Nullable private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) { - if (mSplitScreenController.isPresent() - && mSplitScreenController.get().isSplitScreenVisible()) { + if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) { // We can't look at focused task here as only one task will have focus. - return getSplitScreenDecor(ev); + DesktopModeWindowDecoration splitTaskDecor = getSplitScreenDecor(ev); + return splitTaskDecor == null ? getFocusedDecor() : splitTaskDecor; } else { return getFocusedDecor(); } @@ -711,9 +818,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Nullable private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) { ActivityManager.RunningTaskInfo topOrLeftTask = - mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); + mSplitScreenController.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); ActivityManager.RunningTaskInfo bottomOrRightTask = - mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); + mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); if (topOrLeftTask != null && topOrLeftTask.getConfiguration() .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) { return mWindowDecorByTaskId.get(topOrLeftTask.taskId); @@ -765,12 +872,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; - if (mSplitScreenController.isPresent() - && mSplitScreenController.get().isTaskRootOrStageRoot(taskInfo.taskId)) { + if (mSplitScreenController != null + && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) { return false; } - return DesktopModeStatus.isProto2Enabled() + if (mDesktopModeKeyguardChangeListener.isKeyguardVisibleAndOccluded() + && taskInfo.isFocused) { + return false; + } + return DesktopModeStatus.isEnabled() + && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD + && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop() && mDisplayController.getDisplayContext(taskInfo.displayId) .getResources().getConfiguration().smallestScreenWidthDp >= 600; } @@ -794,16 +907,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { taskSurface, mMainHandler, mMainChoreographer, - mSyncQueue); + mSyncQueue, + mRootTaskDisplayAreaOrganizer); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); + windowDecoration.createResizeVeil(); final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback( - windowDecoration, taskInfo); + windowDecoration); final DesktopModeTouchEventListener touchEventListener = new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback); - windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); - windowDecoration.setCornersListener(mCornersListener); + windowDecoration.setCaptionListeners( + touchEventListener, touchEventListener, touchEventListener); + windowDecoration.setExclusionRegionListener(mExclusionRegionListener); windowDecoration.setDragPositioningCallback(dragPositioningCallback); windowDecoration.setDragDetector(touchEventListener.mDragDetector); windowDecoration.relayout(taskInfo, startT, finishT, @@ -811,28 +927,45 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { incrementEventReceiverTasks(taskInfo.displayId); } private DragPositioningCallback createDragPositioningCallback( - @NonNull DesktopModeWindowDecoration windowDecoration, - @NonNull RunningTaskInfo taskInfo) { - final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width(); - final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth, - getStatusBarHeight(taskInfo.displayId)); + @NonNull DesktopModeWindowDecoration windowDecoration) { + final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize( + R.dimen.desktop_mode_transition_area_height); if (!DesktopModeStatus.isVeiledResizeEnabled()) { return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, - mDisplayController, disallowedAreaForEndBounds, mDragStartListener, - mTransactionFactory); + mDisplayController, mDragStartListener, mTransactionFactory, + transitionAreaHeight); } else { - windowDecoration.createResizeVeil(); return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration, - mDisplayController, disallowedAreaForEndBounds, mDragStartListener, - mTransitions); + mDisplayController, mDragStartListener, mTransitions, + transitionAreaHeight); + } + } + + private RunningTaskInfo getOtherSplitTask(int taskId) { + @SplitPosition int remainingTaskPosition = mSplitScreenController + .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT + ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT; + return mSplitScreenController.getTaskInfo(remainingTaskPosition); + } + + private void closeOtherSplitTask(int taskId) { + if (isTaskInSplitScreen(taskId)) { + mTaskOperations.closeTask(getOtherSplitTask(taskId).token); } } + private boolean isTaskInSplitScreen(int taskId) { + return mSplitScreenController != null + && mSplitScreenController.isTaskInSplitScreen(taskId); + } + private class DragStartListenerImpl implements DragPositioningCallbackUtility.DragStartListener { @Override public void onDragStart(int taskId) { - mWindowDecorByTaskId.get(taskId).closeHandleMenu(); + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + decoration.closeHandleMenu(); + decoration.closeMaximizeMenu(); } } @@ -842,19 +975,33 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } - private class TaskCornersListenerImpl - implements DesktopModeWindowDecoration.TaskCornersListener { + private class ExclusionRegionListenerImpl + implements ExclusionRegionListener { @Override - public void onTaskCornersChanged(int taskId, Region corner) { - mDesktopModeController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner)); - mDesktopTasksController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner)); + public void onExclusionRegionChanged(int taskId, Region region) { + mDesktopTasksController.ifPresent(d -> d.onExclusionRegionChanged(taskId, region)); } @Override - public void onTaskCornersRemoved(int taskId) { - mDesktopModeController.ifPresent(d -> d.removeCornersForTask(taskId)); - mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId)); + public void onExclusionRegionDismissed(int taskId) { + mDesktopTasksController.ifPresent(d -> d.removeExclusionRegionForTask(taskId)); + } + } + + static class DesktopModeKeyguardChangeListener implements KeyguardChangeListener { + private boolean mIsKeyguardVisible; + private boolean mIsKeyguardOccluded; + + @Override + public void onKeyguardVisibilityChanged(boolean visible, boolean occluded, + boolean animatingDismiss) { + mIsKeyguardVisible = visible; + mIsKeyguardOccluded = occluded; + } + + public boolean isKeyguardVisibleAndOccluded() { + return mIsKeyguardVisible && mIsKeyguardOccluded; } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index ce11b2604559..dbff20e6026a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -23,7 +23,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.content.res.TypedArray; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -37,12 +37,16 @@ import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; import android.view.ViewConfiguration; +import android.widget.ImageButton; import android.window.WindowContainerTransaction; +import com.android.internal.policy.ScreenDecorationsUtils; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -69,6 +73,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private DesktopModeWindowDecorationViewHolder mWindowDecorViewHolder; private View.OnClickListener mOnCaptionButtonClickListener; private View.OnTouchListener mOnCaptionTouchListener; + private View.OnLongClickListener mOnCaptionLongClickListener; private DragPositioningCallback mDragPositioningCallback; private DragResizeInputListener mDragResizeListener; private DragDetector mDragDetector; @@ -80,15 +85,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final Point mPositionInParent = new Point(); private HandleMenu mHandleMenu; + private MaximizeMenu mMaximizeMenu; + private ResizeVeil mResizeVeil; private Drawable mAppIcon; private CharSequence mAppName; - private TaskCornersListener mCornersListener; + private ExclusionRegionListener mExclusionRegionListener; private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>(); private int mRelayoutBlock; + private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; DesktopModeWindowDecoration( Context context, @@ -96,38 +104,32 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, + Configuration windowDecorConfig, Handler handler, Choreographer choreographer, - SyncTransactionQueue syncQueue) { - super(context, displayController, taskOrganizer, taskInfo, taskSurface); + SyncTransactionQueue syncQueue, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig); mHandler = handler; mChoreographer = choreographer; mSyncQueue = syncQueue; + mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; loadAppInfo(); } - @Override - protected Configuration getConfigurationWithOverrides( - ActivityManager.RunningTaskInfo taskInfo) { - Configuration configuration = taskInfo.getConfiguration(); - if (DesktopTasksController.isDesktopDensityOverrideSet()) { - // Density is overridden for desktop tasks. Keep system density for window decoration. - configuration.densityDpi = mContext.getResources().getConfiguration().densityDpi; - } - return configuration; - } - void setCaptionListeners( View.OnClickListener onCaptionButtonClickListener, - View.OnTouchListener onCaptionTouchListener) { + View.OnTouchListener onCaptionTouchListener, + View.OnLongClickListener onLongClickListener) { mOnCaptionButtonClickListener = onCaptionButtonClickListener; mOnCaptionTouchListener = onCaptionTouchListener; + mOnCaptionLongClickListener = onLongClickListener; } - void setCornersListener(TaskCornersListener cornersListener) { - mCornersListener = cornersListener; + void setExclusionRegionListener(ExclusionRegionListener exclusionRegionListener) { + mExclusionRegionListener = exclusionRegionListener; } void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) { @@ -178,15 +180,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = windowDecorLayoutId; - mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; + mRelayoutParams.mCaptionHeightId = getCaptionHeightId(); mRelayoutParams.mShadowRadiusId = shadowRadiusID; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; - final TypedArray ta = mContext.obtainStyledAttributes( - new int[]{android.R.attr.dialogCornerRadius}); - mRelayoutParams.mCornerRadius = ta.getDimensionPixelSize(0, 0); - ta.recycle(); + mRelayoutParams.mWindowDecorConfig = DesktopTasksController.isDesktopDensityOverrideSet() + ? mContext.getResources().getConfiguration() // Use system context + : mTaskInfo.configuration; // Use task configuration + mRelayoutParams.mCornerRadius = + (int) ScreenDecorationsUtils.getWindowCornerRadius(mContext); relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo @@ -210,6 +213,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mResult.mRootView, mOnCaptionTouchListener, mOnCaptionButtonClickListener, + mOnCaptionLongClickListener, mAppName, mAppIcon ); @@ -221,9 +225,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (!mTaskInfo.isFocused) { closeHandleMenu(); + closeMaximizeMenu(); } if (!isDragResizeable) { + if (!mTaskInfo.positionInParent.equals(mPositionInParent)) { + // We still want to track caption bar's exclusion region on a non-resizeable task. + updateExclusionRegion(); + } closeDragResizeListener(); return; } @@ -235,8 +244,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mHandler, mChoreographer, mDisplay.getDisplayId(), + mRelayoutParams.mCornerRadius, mDecorationContainerSurface, - mDragPositioningCallback); + mDragPositioningCallback, + mSurfaceControlBuilderSupplier, + mSurfaceControlTransactionSupplier); } final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) @@ -248,13 +260,58 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final int resize_corner = mResult.mRootView.getResources() .getDimensionPixelSize(R.dimen.freeform_resize_corner); - // If either task geometry or position have changed, update this task's cornersListener + // If either task geometry or position have changed, update this task's + // exclusion region listener if (mDragResizeListener.setGeometry( mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop) || !mTaskInfo.positionInParent.equals(mPositionInParent)) { - mCornersListener.onTaskCornersChanged(mTaskInfo.taskId, getGlobalCornersRegion()); + updateExclusionRegion(); + } + + if (isMaximizeMenuActive()) { + if (!mTaskInfo.isVisible()) { + closeMaximizeMenu(); + } else { + mMaximizeMenu.positionMenu(calculateMaximizeMenuPosition(), startT); + } } - mPositionInParent.set(mTaskInfo.positionInParent); + } + + private PointF calculateMaximizeMenuPosition() { + final PointF position = new PointF(); + final Resources resources = mContext.getResources(); + final DisplayLayout displayLayout = + mDisplayController.getDisplayLayout(mTaskInfo.displayId); + if (displayLayout == null) return position; + + final int displayWidth = displayLayout.width(); + final int displayHeight = displayLayout.height(); + final int captionHeight = getCaptionHeight(); + + final ImageButton maximizeWindowButton = + mResult.mRootView.findViewById(R.id.maximize_window); + final int[] maximizeButtonLocation = new int[2]; + maximizeWindowButton.getLocationInWindow(maximizeButtonLocation); + + final int menuWidth = loadDimensionPixelSize( + resources, R.dimen.desktop_mode_maximize_menu_width); + final int menuHeight = loadDimensionPixelSize( + resources, R.dimen.desktop_mode_maximize_menu_height); + + float menuLeft = (mPositionInParent.x + maximizeButtonLocation[0]); + float menuTop = (mPositionInParent.y + captionHeight); + final float menuRight = menuLeft + menuWidth; + final float menuBottom = menuTop + menuHeight; + + // If the menu is out of screen bounds, shift it up/left as needed + if (menuRight > displayWidth) { + menuLeft = (displayWidth - menuWidth); + } + if (menuBottom > displayHeight) { + menuTop = (displayHeight - menuHeight); + } + + return new PointF(menuLeft, menuTop); } boolean isHandleMenuActive() { @@ -294,23 +351,37 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** - * Fade in the resize veil + * Show the resize veil. */ - void showResizeVeil(Rect taskBounds) { + public void showResizeVeil(Rect taskBounds) { mResizeVeil.showVeil(mTaskSurface, taskBounds); } /** + * Show the resize veil. + */ + public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) { + mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */); + } + + /** * Set new bounds for the resize veil */ - void updateResizeVeil(Rect newBounds) { + public void updateResizeVeil(Rect newBounds) { mResizeVeil.updateResizeVeil(newBounds); } /** + * Set new bounds for the resize veil + */ + public void updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds) { + mResizeVeil.updateResizeVeil(tx, newBounds); + } + + /** * Fade the resize veil out. */ - void hideResizeVeil() { + public void hideResizeVeil() { mResizeVeil.hideVeil(); } @@ -321,6 +392,29 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** + * Create and display maximize menu window + */ + void createMaximizeMenu() { + mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer, + mDisplayController, mTaskInfo, mOnCaptionButtonClickListener, mContext, + calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier); + mMaximizeMenu.show(); + } + + /** + * Close the maximize menu window + */ + void closeMaximizeMenu() { + if (!isMaximizeMenuActive()) return; + mMaximizeMenu.close(); + mMaximizeMenu = null; + } + + boolean isMaximizeMenuActive() { + return mMaximizeMenu != null; + } + + /** * Create and display handle menu window */ void createHandleMenu() { @@ -331,7 +425,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .setOnTouchListener(mOnCaptionTouchListener) .setLayoutId(mRelayoutParams.mLayoutResId) .setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY) - .setWindowingButtonsVisible(DesktopModeStatus.isProto2Enabled()) + .setWindowingButtonsVisible(DesktopModeStatus.isEnabled()) .build(); mHandleMenu.show(); } @@ -446,27 +540,43 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin public void close() { closeDragResizeListener(); closeHandleMenu(); - mCornersListener.onTaskCornersRemoved(mTaskInfo.taskId); + mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId); disposeResizeVeil(); super.close(); } private int getDesktopModeWindowDecorLayoutId(int windowingMode) { - if (DesktopModeStatus.isProto1Enabled()) { - return R.layout.desktop_mode_app_controls_window_decor; - } return windowingMode == WINDOWING_MODE_FREEFORM ? R.layout.desktop_mode_app_controls_window_decor : R.layout.desktop_mode_focused_window_decor; } + private void updatePositionInParent() { + mPositionInParent.set(mTaskInfo.positionInParent); + } + + private void updateExclusionRegion() { + // An outdated position in parent is one reason for this to be called; update it here. + updatePositionInParent(); + mExclusionRegionListener + .onExclusionRegionChanged(mTaskInfo.taskId, getGlobalExclusionRegion()); + } + /** - * Create a new region out of the corner rects of this task. + * Create a new exclusion region from the corner rects (if resizeable) and caption bounds + * of this task. */ - Region getGlobalCornersRegion() { - Region cornersRegion = mDragResizeListener.getCornersRegion(); - cornersRegion.translate(mPositionInParent.x, mPositionInParent.y); - return cornersRegion; + private Region getGlobalExclusionRegion() { + Region exclusionRegion; + if (mTaskInfo.isResizeable) { + exclusionRegion = mDragResizeListener.getCornersRegion(); + } else { + exclusionRegion = new Region(); + } + exclusionRegion.union(new Rect(0, 0, mResult.mWidth, + getCaptionHeight())); + exclusionRegion.translate(mPositionInParent.x, mPositionInParent.y); + return exclusionRegion; } /** @@ -479,6 +589,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } } + @Override + int getCaptionHeightId() { + return R.dimen.freeform_decor_caption_height; + } + + private int getCaptionHeight() { + return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId()); + } + /** * Add transition to mTransitionsPausingRelayout */ @@ -513,25 +632,34 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SurfaceControl taskSurface, Handler handler, Choreographer choreographer, - SyncTransactionQueue syncQueue) { + SyncTransactionQueue syncQueue, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + final Configuration windowDecorConfig = + DesktopTasksController.isDesktopDensityOverrideSet() + ? context.getResources().getConfiguration() // Use system context + : taskInfo.configuration; // Use task configuration return new DesktopModeWindowDecoration( context, displayController, taskOrganizer, taskInfo, taskSurface, + windowDecorConfig, handler, choreographer, - syncQueue); + syncQueue, + rootTaskDisplayAreaOrganizer); } } - interface TaskCornersListener { - /** Inform the implementing class of this task's change in corner resize handles */ - void onTaskCornersChanged(int taskId, Region corner); + interface ExclusionRegionListener { + /** Inform the implementing class of this task's change in region resize handles */ + void onExclusionRegionChanged(int taskId, Region region); - /** Inform the implementing class that this task no longer needs its corners tracked, - * likely due to it closing. */ - void onTaskCornersRemoved(int taskId); + /** + * Inform the implementing class that this task no longer needs an exclusion region, + * likely due to it closing. + */ + void onExclusionRegionDismissed(int taskId); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java index 65b5a7a17afe..da268988bac7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java @@ -24,6 +24,9 @@ import static android.view.MotionEvent.ACTION_UP; import android.graphics.PointF; import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.Nullable; /** * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow @@ -54,14 +57,24 @@ class DragDetector { * * @return the result returned by {@link #mEventHandler}, or the result when * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed - */ + */ boolean onMotionEvent(MotionEvent ev) { + return onMotionEvent(null /* view */, ev); + } + + /** + * The receiver of the {@link MotionEvent} flow. + * + * @return the result returned by {@link #mEventHandler}, or the result when + * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed + */ + boolean onMotionEvent(View v, MotionEvent ev) { final boolean isTouchScreen = (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; if (!isTouchScreen) { // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered // to take the slop threshold into consideration. - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } switch (ev.getActionMasked()) { case ACTION_DOWN: { @@ -69,12 +82,15 @@ class DragDetector { float rawX = ev.getRawX(0); float rawY = ev.getRawY(0); mInputDownPoint.set(rawX, rawY); - mResultOfDownAction = mEventHandler.handleMotionEvent(ev); + mResultOfDownAction = mEventHandler.handleMotionEvent(v, ev); return mResultOfDownAction; } case ACTION_MOVE: { + if (ev.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = ev.getPointerId(0); + } + final int dragPointerIndex = ev.findPointerIndex(mDragPointerId); if (!mIsDragEvent) { - int dragPointerIndex = ev.findPointerIndex(mDragPointerId); float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x; float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y; // Touches generate noisy moves, so only once the move is past the touch @@ -84,7 +100,7 @@ class DragDetector { // The event handler should only be notified about 'move' events if a drag has been // detected. if (mIsDragEvent) { - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } else { return mResultOfDownAction; } @@ -92,10 +108,10 @@ class DragDetector { case ACTION_UP: case ACTION_CANCEL: { resetState(); - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } default: - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } } @@ -111,6 +127,6 @@ class DragDetector { } interface MotionEventHandler { - boolean handleMotionEvent(MotionEvent ev); + boolean handleMotionEvent(@Nullable View v, MotionEvent ev); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java index 4e98f0c3a48a..1669cf4a222c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java @@ -17,12 +17,15 @@ package com.android.wm.shell.windowdecor; import android.annotation.IntDef; +import android.graphics.Rect; /** * Callback called when receiving drag-resize or drag-move related input events. */ public interface DragPositioningCallback { - @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM}) + @IntDef(flag = true, value = { + CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM + }) @interface CtrlType {} int CTRL_TYPE_UNDEFINED = 0; @@ -44,13 +47,15 @@ public interface DragPositioningCallback { * Called when the pointer moves during a drag-resize or drag-move. * @param x x coordinate in window decoration coordinate system of the new pointer location * @param y y coordinate in window decoration coordinate system of the new pointer location + * @return the updated task bounds */ - void onDragPositioningMove(float x, float y); + Rect onDragPositioningMove(float x, float y); /** * Called when a drag-resize or drag-move stops. * @param x x coordinate in window decoration coordinate system where the drag resize stops * @param y y coordinate in window decoration coordinate system where the drag resize stops + * @return the final bounds for the dragged task */ - void onDragPositioningEnd(float x, float y); + Rect onDragPositioningEnd(float x, float y); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java index 09e29bcbcf9f..cb0a6c733fe3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java @@ -83,8 +83,6 @@ public class DragPositioningCallbackUtility { // Make sure the new resizing destination in any direction falls within the stable bounds. // If not, set the bounds back to the old location that was valid to avoid conflicts with // some regions such as the gesture area. - displayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId()) - .getStableBounds(stableBounds); if ((ctrlType & CTRL_TYPE_LEFT) != 0) { final int candidateLeft = repositionTaskBounds.left + (int) delta.x; repositionTaskBounds.left = (candidateLeft > stableBounds.left) @@ -136,7 +134,7 @@ public class DragPositioningCallbackUtility { repositionTaskBounds.top); } - static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, + private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, PointF repositionStartPoint, float x, float y) { final float deltaX = x - repositionStartPoint.x; final float deltaY = y - repositionStartPoint.y; @@ -145,6 +143,41 @@ public class DragPositioningCallbackUtility { } /** + * Calculates the new position of the top edge of the task and returns true if it is below the + * disallowed area. + * + * @param disallowedAreaForEndBoundsHeight the height of the area that where the task positioner + * should not finalize the bounds using WCT#setBounds + * @param taskBoundsAtDragStart the bounds of the task on the first drag input event + * @param repositionStartPoint initial input coordinate + * @param y the y position of the motion event + * @return true if the top of the task is below the disallowed area + */ + static boolean isBelowDisallowedArea(int disallowedAreaForEndBoundsHeight, + Rect taskBoundsAtDragStart, PointF repositionStartPoint, float y) { + final float deltaY = y - repositionStartPoint.y; + final float topPosition = taskBoundsAtDragStart.top + deltaY; + return topPosition > disallowedAreaForEndBoundsHeight; + } + + /** + * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If + * the bounds are outside of the stable bounds, they are shifted to place task at the top of the + * stable bounds. + */ + static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds, + PointF repositionStartPoint, float x, float y) { + updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint, + x, y); + + // If task is outside of stable bounds (in the status bar area), shift the task down. + if (stableBounds.top > repositionTaskBounds.top) { + final int yShift = stableBounds.top - repositionTaskBounds.top; + repositionTaskBounds.offset(0, yShift); + } + } + + /** * Apply a bounds change to a task. * @param windowDecoration decor of task we are changing bounds for * @param taskBounds new bounds of this task 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 287d86187288..7c6fb99e9c8b 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 @@ -18,8 +18,11 @@ package com.android.wm.shell.windowdecor; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; @@ -42,11 +45,14 @@ import android.view.InputEventReceiver; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.SurfaceControl; +import android.view.View; import android.view.ViewConfiguration; import android.view.WindowManagerGlobal; import com.android.internal.view.BaseIWindow; +import java.util.function.Supplier; + /** * An input event listener registered to InputDispatcher to receive input events on task edges and * and corners. Converts them to drag resize requests. @@ -55,11 +61,11 @@ import com.android.internal.view.BaseIWindow; */ class DragResizeInputListener implements AutoCloseable { private static final String TAG = "DragResizeInputListener"; - private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession(); private final Handler mHandler; private final Choreographer mChoreographer; private final InputManager mInputManager; + private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; private final int mDisplayId; private final BaseIWindow mFakeWindow; @@ -69,10 +75,15 @@ class DragResizeInputListener implements AutoCloseable { private final TaskResizeInputEventReceiver mInputEventReceiver; private final DragPositioningCallback mCallback; + private final SurfaceControl mInputSinkSurface; + private final BaseIWindow mFakeSinkWindow; + private final InputChannel mSinkInputChannel; + private int mTaskWidth; private int mTaskHeight; private int mResizeHandleThickness; private int mCornerSize; + private int mTaskCornerRadius; private Rect mLeftTopCornerBounds; private Rect mRightTopCornerBounds; @@ -87,15 +98,20 @@ class DragResizeInputListener implements AutoCloseable { Handler handler, Choreographer choreographer, int displayId, + int taskCornerRadius, SurfaceControl decorationSurface, - DragPositioningCallback callback) { + DragPositioningCallback callback, + Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, + Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) { mInputManager = context.getSystemService(InputManager.class); mHandler = handler; mChoreographer = choreographer; + mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; mDisplayId = displayId; + mTaskCornerRadius = taskCornerRadius; mDecorationSurface = decorationSurface; - // Use a fake window as the backing surface is a container layer and we don't want to create - // a buffer layer for it so we can't use ViewRootImpl. + // Use a fake window as the backing surface is a container layer, and we don't want to + // create a buffer layer for it, so we can't use ViewRootImpl. mFakeWindow = new BaseIWindow(); mFakeWindow.setSession(mWindowSession); mFocusGrantToken = new Binder(); @@ -108,7 +124,7 @@ class DragResizeInputListener implements AutoCloseable { null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, - 0 /* inputFeatures */, + INPUT_FEATURE_SPY, TYPE_APPLICATION, null /* windowToken */, mFocusGrantToken, @@ -123,15 +139,39 @@ class DragResizeInputListener implements AutoCloseable { mCallback = callback; mDragDetector = new DragDetector(mInputEventReceiver); mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop()); + + mInputSinkSurface = surfaceControlBuilderSupplier.get() + .setName("TaskInputSink of " + decorationSurface) + .setContainerLayer() + .setParent(mDecorationSurface) + .build(); + mSurfaceControlTransactionSupplier.get() + .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER) + .show(mInputSinkSurface) + .apply(); + mFakeSinkWindow = new BaseIWindow(); + mSinkInputChannel = new InputChannel(); + try { + mWindowSession.grantInputChannel( + mDisplayId, + mInputSinkSurface, + mFakeSinkWindow, + null /* hostInputToken */, + FLAG_NOT_FOCUSABLE, + 0 /* privateFlags */, + INPUT_FEATURE_NO_INPUT_CHANNEL, + TYPE_INPUT_CONSUMER, + null /* windowToken */, + mFocusGrantToken, + "TaskInputSink of " + decorationSurface, + mSinkInputChannel); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** - * Updates geometry of this drag resize handler. Needs to be called every time there is a size - * change to notify the input event receiver it's ready to take the next input event. Otherwise - * it'll keep batching move events and the drag resize process is stalled. - * - * This is also used to update the touch regions of this handler every event dispatched here is - * a potential resize request. + * Updates the geometry (the touch region) of this drag resize handler. * * @param taskWidth The width of the task. * @param taskHeight The height of the task. @@ -221,7 +261,35 @@ class DragResizeInputListener implements AutoCloseable { mDecorationSurface, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, - 0 /* inputFeatures */, + INPUT_FEATURE_SPY, + touchRegion); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + mSurfaceControlTransactionSupplier.get() + .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight) + .apply(); + // The touch region of the TaskInputSink should be the touch region of this + // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent + // input windows from handling down events, which will bring tasks in the back to front. + // + // Note not the entire touch region responds to both mouse and touchscreen events. + // Therefore, in the region that only responds to one of them, it would be a no-op to + // perform a gesture in the other type of events. We currently only have a mouse-only region + // out of the task bounds, and due to the roughness of touchscreen events, it's not a severe + // issue. However, were there touchscreen-only a region out of the task bounds, mouse + // gestures will become no-op in that region, even though the mouse gestures may appear to + // be performed on the input window behind the resize handle. + touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE); + try { + mWindowSession.updateInputChannel( + mSinkInputChannel.getToken(), + mDisplayId, + mInputSinkSurface, + FLAG_NOT_FOCUSABLE, + 0 /* privateFlags */, + INPUT_FEATURE_NO_INPUT_CHANNEL, touchRegion); } catch (RemoteException e) { e.rethrowFromSystemServer(); @@ -250,6 +318,16 @@ class DragResizeInputListener implements AutoCloseable { } catch (RemoteException e) { e.rethrowFromSystemServer(); } + + mSinkInputChannel.dispose(); + try { + mWindowSession.remove(mFakeSinkWindow); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + mSurfaceControlTransactionSupplier.get() + .remove(mInputSinkSurface) + .apply(); } private class TaskResizeInputEventReceiver extends InputEventReceiver @@ -258,6 +336,7 @@ class DragResizeInputListener implements AutoCloseable { private final Runnable mConsumeBatchEventRunnable; private boolean mConsumeBatchEventScheduled; private boolean mShouldHandleEvents; + private int mLastCursorType = PointerIcon.TYPE_DEFAULT; private TaskResizeInputEventReceiver( InputChannel inputChannel, Handler handler, Choreographer choreographer) { @@ -303,7 +382,7 @@ class DragResizeInputListener implements AutoCloseable { } @Override - public boolean handleMotionEvent(MotionEvent e) { + public boolean handleMotionEvent(View v, MotionEvent e) { boolean result = false; // Check if this is a touch event vs mouse event. // Touch events are tracked in four corners. Other events are tracked in resize edges. @@ -318,6 +397,8 @@ class DragResizeInputListener implements AutoCloseable { mShouldHandleEvents = isInResizeHandleBounds(x, y); } if (mShouldHandleEvents) { + mInputManager.pilferPointers(mInputChannel.getToken()); + mDragPointerId = e.getPointerId(0); float rawX = e.getRawX(0); float rawY = e.getRawY(0); @@ -357,7 +438,6 @@ class DragResizeInputListener implements AutoCloseable { break; } case MotionEvent.ACTION_HOVER_EXIT: - mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT); result = true; break; } @@ -383,19 +463,71 @@ class DragResizeInputListener implements AutoCloseable { @DragPositioningCallback.CtrlType private int calculateResizeHandlesCtrlType(float x, float y) { int ctrlType = 0; - if (x < 0) { + // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with + // sides will use the bounds specified in setGeometry and not go into task bounds. + if (x < mTaskCornerRadius) { ctrlType |= CTRL_TYPE_LEFT; } - if (x > mTaskWidth) { + if (x > mTaskWidth - mTaskCornerRadius) { ctrlType |= CTRL_TYPE_RIGHT; } - if (y < 0) { + if (y < mTaskCornerRadius) { ctrlType |= CTRL_TYPE_TOP; } - if (y > mTaskHeight) { + if (y > mTaskHeight - mTaskCornerRadius) { ctrlType |= CTRL_TYPE_BOTTOM; } - return ctrlType; + // Check distances from the center if it's in one of four corners. + if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0 + && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) { + return checkDistanceFromCenter(ctrlType, x, y); + } + // Otherwise, we should make sure we don't resize tasks inside task bounds. + return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0; + } + + // If corner input is not within appropriate distance of corner radius, do not use it. + // If input is not on a corner or is within valid distance, return ctrlType. + @DragPositioningCallback.CtrlType + private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType, + float x, float y) { + int centerX; + int centerY; + + // Determine center of rounded corner circle; this is simply the corner if radius is 0. + switch (ctrlType) { + case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: { + centerX = mTaskCornerRadius; + centerY = mTaskCornerRadius; + break; + } + case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: { + centerX = mTaskCornerRadius; + centerY = mTaskHeight - mTaskCornerRadius; + break; + } + case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: { + centerX = mTaskWidth - mTaskCornerRadius; + centerY = mTaskCornerRadius; + break; + } + case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: { + centerX = mTaskWidth - mTaskCornerRadius; + centerY = mTaskHeight - mTaskCornerRadius; + break; + } + default: { + throw new IllegalArgumentException("ctrlType should be complex, but it's 0x" + + Integer.toHexString(ctrlType)); + } + } + double distanceFromCenter = Math.hypot(x - centerX, y - centerY); + + if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness + && distanceFromCenter >= mTaskCornerRadius) { + return ctrlType; + } + return 0; } @DragPositioningCallback.CtrlType @@ -439,7 +571,19 @@ class DragResizeInputListener implements AutoCloseable { cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; break; } - mInputManager.setPointerIconType(cursorType); + // Only update the cursor type to default once so that views behind the decor container + // layer that aren't in the active resizing regions have chances to update the cursor + // type. We would like to enforce the cursor type by setting the cursor type multilple + // times in active regions because we shouldn't allow the views behind to change it, as + // we'll pilfer the gesture initiated in this area. This is necessary because 1) we + // should allow the views behind regions only for touches to set the cursor type; and 2) + // there is a small region out of each rounded corner that's inside the task bounds, + // where views in the task can receive input events because we can't set touch regions + // of input sinks to have rounded corners. + if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) { + mInputManager.setPointerIconType(cursorType); + mLastCursorType = cursorType; + } } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java index 9082323452c9..389db62ef9d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java @@ -21,8 +21,6 @@ import android.graphics.Rect; import android.view.SurfaceControl; import android.window.WindowContainerTransaction; -import androidx.annotation.Nullable; - import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -42,28 +40,29 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); - // If a task move (not resize) finishes in this region, the positioner will not attempt to + // If a task move (not resize) finishes with the positions y less than this value, do not // finalize the bounds there using WCT#setBounds - private final Rect mDisallowedAreaForEndBounds; + private final int mDisallowedAreaForEndBoundsHeight; private boolean mHasDragResized; private int mCtrlType; FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, - DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) { - this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds, - dragStartListener -> {}, SurfaceControl.Transaction::new); + DisplayController displayController, int disallowedAreaForEndBoundsHeight) { + this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {}, + SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight); } FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, - DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds, + DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Supplier<SurfaceControl.Transaction> supplier) { + Supplier<SurfaceControl.Transaction> supplier, + int disallowedAreaForEndBoundsHeight) { mTaskOrganizer = taskOrganizer; mWindowDecoration = windowDecoration; mDisplayController = displayController; - mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds); mDragStartListener = dragStartListener; mTransactionSupplier = supplier; + mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight; } @Override @@ -79,10 +78,14 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { mTaskOrganizer.applyTransaction(wct); } mRepositionTaskBounds.set(mTaskBoundsAtDragStart); + if (mStableBounds.isEmpty()) { + mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId()) + .getStableBounds(mStableBounds); + } } @Override - public void onDragPositioningMove(float x, float y) { + public Rect onDragPositioningMove(float x, float y) { final WindowContainerTransaction wct = new WindowContainerTransaction(); PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType, @@ -103,10 +106,11 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); t.apply(); } + return new Rect(mRepositionTaskBounds); } @Override - public void onDragPositioningEnd(float x, float y) { + public Rect onDragPositioningEnd(float x, float y) { // If task has been resized or task was dragged into area outside of // mDisallowedAreaForEndBounds, apply WCT to finish it. if (isResizing() && mHasDragResized) { @@ -121,10 +125,12 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { } mTaskOrganizer.applyTransaction(wct); } else if (mCtrlType == CTRL_TYPE_UNDEFINED - && !mDisallowedAreaForEndBounds.contains((int) x, (int) y)) { + && DragPositioningCallbackUtility.isBelowDisallowedArea( + mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint, + y)) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, - mTaskBoundsAtDragStart, mRepositionStartPoint, x, y); + DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds, + mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y); wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds); mTaskOrganizer.applyTransaction(wct); } @@ -133,6 +139,7 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { mRepositionStartPoint.set(0, 0); mCtrlType = CTRL_TYPE_UNDEFINED; mHasDragResized = false; + return new Rect(mRepositionTaskBounds); } private boolean isResizing() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java index ac4a597c15d1..1ec8a1749b55 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java @@ -186,7 +186,12 @@ class HandleMenu { // More Actions pill setup. final View moreActionsPillView = mMoreActionsPill.mWindowViewHost.getView(); final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button); - closeBtn.setOnClickListener(mOnClickListener); + if (shouldShowCloseButton()) { + closeBtn.setVisibility(View.GONE); + } else { + closeBtn.setVisibility(View.VISIBLE); + closeBtn.setOnClickListener(mOnClickListener); + } final Button selectBtn = moreActionsPillView.findViewById(R.id.select_button); selectBtn.setOnClickListener(mOnClickListener); } @@ -228,7 +233,6 @@ class HandleMenu { /** * Update pill layout, in case task changes have caused positioning to change. - * @param t */ void relayout(SurfaceControl.Transaction t) { if (mAppInfoPill != null) { @@ -236,7 +240,7 @@ class HandleMenu { t.setPosition(mAppInfoPill.mWindowSurface, mAppInfoPillPosition.x, mAppInfoPillPosition.y); // Only show windowing buttons in proto2. Proto1 uses a system-level mode only. - final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled(); + final boolean shouldShowWindowingPill = DesktopModeStatus.isEnabled(); if (shouldShowWindowingPill) { t.setPosition(mWindowingPill.mWindowSurface, mWindowingPillPosition.x, mWindowingPillPosition.y); @@ -245,10 +249,12 @@ class HandleMenu { mMoreActionsPillPosition.x, mMoreActionsPillPosition.y); } } + /** * Check a passed MotionEvent if a click has occurred on any button on this caption * Note this should only be called when a regular onClick is not possible * (i.e. the button was clicked through status bar layer) + * * @param ev the MotionEvent to compare against. */ void checkClickEvent(MotionEvent ev) { @@ -267,6 +273,7 @@ class HandleMenu { * A valid menu input is one of the following: * An input that happens in the menu views. * Any input before the views have been laid out. + * * @param inputPoint the input to compare against. */ boolean isValidMenuInput(PointF inputPoint) { @@ -297,7 +304,6 @@ class HandleMenu { /** * Check if the views for handle menu can be seen. - * @return */ private boolean viewsLaidOut() { return mAppInfoPill.mWindowViewHost.getView().isLaidOut(); @@ -318,8 +324,11 @@ class HandleMenu { R.dimen.desktop_mode_handle_menu_app_info_pill_height); mWindowingPillHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_windowing_pill_height); - mMoreActionsPillHeight = loadDimensionPixelSize(resources, - R.dimen.desktop_mode_handle_menu_more_actions_pill_height); + mMoreActionsPillHeight = shouldShowCloseButton() + ? loadDimensionPixelSize(resources, + R.dimen.desktop_mode_handle_menu_more_actions_pill_freeform_height) + : loadDimensionPixelSize(resources, + R.dimen.desktop_mode_handle_menu_more_actions_pill_height); mShadowRadius = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_shadow_radius); mCornerRadius = loadDimensionPixelSize(resources, @@ -333,6 +342,10 @@ class HandleMenu { return resources.getDimensionPixelSize(resourceId); } + private boolean shouldShowCloseButton() { + return mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM; + } + void close() { mAppInfoPill.releaseView(); mAppInfoPill = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt new file mode 100644 index 000000000000..050d1e9df392 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt @@ -0,0 +1,154 @@ +/* + * 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.windowdecor + +import android.app.ActivityManager.RunningTaskInfo +import android.content.Context +import android.content.res.Resources +import android.graphics.PixelFormat +import android.graphics.PointF +import android.view.LayoutInflater +import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction +import android.view.SurfaceControlViewHost +import android.view.View.OnClickListener +import android.view.WindowManager +import android.view.WindowlessWindowManager +import android.widget.Button +import android.window.TaskConstants +import com.android.wm.shell.R +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.windowdecor.WindowDecoration.AdditionalWindow +import java.util.function.Supplier + + +/** + * Menu that appears when user long clicks the maximize button. Gives the user the option to + * maximize the task or snap the task to the right or left half of the screen. + */ +class MaximizeMenu( + private val syncQueue: SyncTransactionQueue, + private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer, + private val displayController: DisplayController, + private val taskInfo: RunningTaskInfo, + private val onClickListener: OnClickListener, + private val decorWindowContext: Context, + private val menuPosition: PointF, + private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() } +) { + private var maximizeMenu: AdditionalWindow? = null + private lateinit var viewHost: SurfaceControlViewHost + private lateinit var leash: SurfaceControl + private val shadowRadius = loadDimensionPixelSize( + R.dimen.desktop_mode_maximize_menu_shadow_radius + ).toFloat() + private val cornerRadius = loadDimensionPixelSize( + R.dimen.desktop_mode_maximize_menu_corner_radius + ).toFloat() + + /** Position the menu relative to the caption's position. */ + fun positionMenu(position: PointF, t: Transaction) { + menuPosition.set(position) + t.setPosition(leash, menuPosition.x, menuPosition.y) + } + + /** Creates and shows the maximize window. */ + fun show() { + if (maximizeMenu != null) return + createMaximizeMenu() + setupMaximizeMenu() + } + + /** Closes the maximize window and releases its view. */ + fun close() { + maximizeMenu?.releaseView() + maximizeMenu = null + } + + /** Create a maximize menu that is attached to the display area. */ + private fun createMaximizeMenu() { + val t = transactionSupplier.get() + val v = LayoutInflater.from(decorWindowContext).inflate( + R.layout.desktop_mode_window_decor_maximize_menu, + null // Root + ) + val builder = SurfaceControl.Builder() + rootTdaOrganizer.attachToDisplayArea(taskInfo.displayId, builder) + leash = builder + .setName("Maximize Menu") + .setContainerLayer() + .build() + val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width) + val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height) + val lp = WindowManager.LayoutParams( + menuWidth, + menuHeight, + WindowManager.LayoutParams.TYPE_APPLICATION, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSPARENT + ) + lp.title = "Maximize Menu for Task=" + taskInfo.taskId + lp.setTrustedOverlay() + val windowManager = WindowlessWindowManager( + taskInfo.configuration, + leash, + null // HostInputToken + ) + viewHost = SurfaceControlViewHost(decorWindowContext, + displayController.getDisplay(taskInfo.displayId), windowManager, + "MaximizeMenu") + viewHost.setView(v, lp) + + // Bring menu to front when open + t.setLayer(leash, TaskConstants.TASK_CHILD_LAYER_FLOATING_MENU) + .setPosition(leash, menuPosition.x, menuPosition.y) + .setWindowCrop(leash, menuWidth, menuHeight) + .setShadowRadius(leash, shadowRadius) + .setCornerRadius(leash, cornerRadius) + .show(leash) + maximizeMenu = AdditionalWindow(leash, viewHost, transactionSupplier) + + syncQueue.runInSync { transaction -> + transaction.merge(t) + t.close() + } + } + + private fun loadDimensionPixelSize(resourceId: Int): Int { + return if (resourceId == Resources.ID_NULL) { + 0 + } else { + decorWindowContext.resources.getDimensionPixelSize(resourceId) + } + } + + private fun setupMaximizeMenu() { + val maximizeMenuView = maximizeMenu?.mWindowViewHost?.view ?: return + + maximizeMenuView.requireViewById<Button>( + R.id.maximize_menu_maximize_button + ).setOnClickListener(onClickListener) + maximizeMenuView.requireViewById<Button>( + R.id.maximize_menu_snap_right_button + ).setOnClickListener(onClickListener) + maximizeMenuView.requireViewById<Button>( + R.id.maximize_menu_snap_left_button + ).setOnClickListener(onClickListener) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt new file mode 100644 index 000000000000..b2267ddb6ba7 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt @@ -0,0 +1,72 @@ +package com.android.wm.shell.windowdecor + +import android.animation.ValueAnimator +import android.app.ActivityManager.RunningTaskInfo +import android.graphics.PointF +import android.graphics.Rect +import android.view.MotionEvent +import android.view.SurfaceControl + +/** + * Creates an animator to shrink and position task after a user drags a fullscreen task from + * the top of the screen to transition it into freeform and before the user releases the task. The + * MoveToDesktopAnimator object also holds information about the state of the task that are + * accessed by the EnterDesktopTaskTransitionHandler. + */ +class MoveToDesktopAnimator @JvmOverloads constructor( + private val startBounds: Rect, + private val taskInfo: RunningTaskInfo, + private val taskSurface: SurfaceControl, + private val transactionFactory: () -> SurfaceControl.Transaction = + SurfaceControl::Transaction +) { + companion object { + // The size of the screen during drag relative to the fullscreen size + const val DRAG_FREEFORM_SCALE: Float = 0.4f + const val ANIMATION_DURATION = 336 + } + + private val animatedTaskWidth + get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width() + private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f, + DRAG_FREEFORM_SCALE) + .setDuration(ANIMATION_DURATION.toLong()) + .apply { + val t = SurfaceControl.Transaction() + addUpdateListener { animation -> + val animatorValue = animation.animatedValue as Float + t.setScale(taskSurface, animatorValue, animatorValue) + .apply() + } + } + + val taskId get() = taskInfo.taskId + val position: PointF = PointF(0.0f, 0.0f) + + /** + * Starts the animation that scales the task down. + */ + fun startAnimation() { + dragToDesktopAnimator.start() + } + + /** + * Uses the position of the motion event and the current scale of the task as defined by the + * ValueAnimator to update the local position variable and set the task surface's position + */ + fun updatePosition(ev: MotionEvent) { + position.x = ev.x - animatedTaskWidth / 2 + position.y = ev.y + + val t = transactionFactory() + t.setPosition(taskSurface, position.x, position.y) + t.apply() + } + + /** + * Ends the animation, setting the scale and position to the final animation value + */ + fun endAnimator() { + dragToDesktopAnimator.end() + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java index 82771095cd82..09fc3dacf6f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java @@ -49,11 +49,13 @@ public class ResizeVeil { private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier; private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; private final Drawable mAppIcon; + private ImageView mIconView; private SurfaceControl mParentSurface; private SurfaceControl mVeilSurface; private final RunningTaskInfo mTaskInfo; private SurfaceControlViewHost mViewHost; private final Display mDisplay; + private ValueAnimator mVeilAnimator; public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display, @@ -97,16 +99,22 @@ public class ResizeVeil { mViewHost = new SurfaceControlViewHost(mContext, mDisplay, windowManager, "ResizeVeil"); mViewHost.setView(v, lp); - final ImageView appIcon = mViewHost.getView().findViewById(R.id.veil_application_icon); - appIcon.setImageDrawable(mAppIcon); + mIconView = mViewHost.getView().findViewById(R.id.veil_application_icon); + mIconView.setImageDrawable(mAppIcon); } /** - * Animate veil's alpha to 1, fading it in. + * Shows the veil surface/view. + * + * @param t the transaction to apply in sync with the veil draw + * @param parentSurface the surface that the veil should be a child of + * @param taskBounds the bounds of the task that owns the veil + * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown + * immediately */ - public void showVeil(SurfaceControl parentSurface, Rect taskBounds) { + public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface, + Rect taskBounds, boolean fadeIn) { // Parent surface can change, ensure it is up to date. - SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); if (!parentSurface.equals(mParentSurface)) { t.reparent(mVeilSurface, parentSurface); mParentSurface = parentSurface; @@ -115,22 +123,46 @@ public class ResizeVeil { int backgroundColorId = getBackgroundColorId(); mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId)); - final ValueAnimator animator = new ValueAnimator(); - animator.setFloatValues(0f, 1f); - animator.setDuration(RESIZE_ALPHA_DURATION); - animator.addUpdateListener(animation -> { - t.setAlpha(mVeilSurface, animator.getAnimatedFraction()); - t.apply(); - }); - relayout(taskBounds, t); - t.show(mVeilSurface) - .addTransactionCommittedListener(mContext.getMainExecutor(), () -> animator.start()) - .setAlpha(mVeilSurface, 0); + if (fadeIn) { + mVeilAnimator = new ValueAnimator(); + mVeilAnimator.setFloatValues(0f, 1f); + mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION); + mVeilAnimator.addUpdateListener(animation -> { + t.setAlpha(mVeilSurface, mVeilAnimator.getAnimatedFraction()); + t.apply(); + }); + + final ValueAnimator iconAnimator = new ValueAnimator(); + iconAnimator.setFloatValues(0f, 1f); + iconAnimator.setDuration(RESIZE_ALPHA_DURATION); + iconAnimator.addUpdateListener(animation -> { + mIconView.setAlpha(animation.getAnimatedFraction()); + }); + + t.show(mVeilSurface) + .addTransactionCommittedListener( + mContext.getMainExecutor(), () -> { + mVeilAnimator.start(); + iconAnimator.start(); + }) + .setAlpha(mVeilSurface, 0); + } else { + // Show the veil immediately at full opacity. + t.show(mVeilSurface).setAlpha(mVeilSurface, 1); + } mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t); } /** + * Animate veil's alpha to 1, fading it in. + */ + public void showVeil(SurfaceControl parentSurface, Rect taskBounds) { + SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); + showVeil(t, parentSurface, taskBounds, true /* fadeIn */); + } + + /** * Update veil bounds to match bounds changes. * @param newBounds bounds to update veil to. */ @@ -147,6 +179,22 @@ public class ResizeVeil { */ public void updateResizeVeil(Rect newBounds) { SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); + updateResizeVeil(t, newBounds); + } + + /** + * Calls relayout to update task and veil bounds. + * Finishes veil fade in if animation is currently running; this is to prevent empty space + * being visible behind the transparent veil during a fast resize. + * + * @param t a transaction to be applied in sync with the veil draw. + * @param newBounds bounds to update veil to. + */ + public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) { + if (mVeilAnimator != null && mVeilAnimator.isStarted()) { + // TODO(b/300145351): Investigate why ValueAnimator#end does not work here. + mVeilAnimator.setCurrentPlayTime(RESIZE_ALPHA_DURATION); + } relayout(newBounds, t); mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 58c78e6a5b9f..303954a2d9fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -53,33 +53,33 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); - // If a task move (not resize) finishes in this region, the positioner will not attempt to + // If a task move (not resize) finishes with the positions y less than this value, do not // finalize the bounds there using WCT#setBounds - private final Rect mDisallowedAreaForEndBounds = new Rect(); + private final int mDisallowedAreaForEndBoundsHeight; private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; private int mCtrlType; public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, - Rect disallowedAreaForEndBounds, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Transitions transitions) { - this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds, - dragStartListener, SurfaceControl.Transaction::new, transitions); + Transitions transitions, + int disallowedAreaForEndBoundsHeight) { + this(taskOrganizer, windowDecoration, displayController, dragStartListener, + SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight); } public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, - Rect disallowedAreaForEndBounds, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) { + Supplier<SurfaceControl.Transaction> supplier, Transitions transitions, + int disallowedAreaForEndBoundsHeight) { mTaskOrganizer = taskOrganizer; mDesktopWindowDecoration = windowDecoration; mDisplayController = displayController; mDragStartListener = dragStartListener; - mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds); mTransactionSupplier = supplier; mTransitions = transitions; + mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight; } @Override @@ -98,10 +98,14 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, } mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId); mRepositionTaskBounds.set(mTaskBoundsAtDragStart); + if (mStableBounds.isEmpty()) { + mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId()) + .getStableBounds(mStableBounds); + } } @Override - public void onDragPositioningMove(float x, float y) { + public Rect onDragPositioningMove(float x, float y) { PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType, mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, @@ -110,14 +114,14 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, } else if (mCtrlType == CTRL_TYPE_UNDEFINED) { final SurfaceControl.Transaction t = mTransactionSupplier.get(); DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration, - mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, - x, y); + mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); t.apply(); } + return new Rect(mRepositionTaskBounds); } @Override - public void onDragPositioningEnd(float x, float y) { + public Rect onDragPositioningEnd(float x, float y) { PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); if (isResizing()) { @@ -138,9 +142,11 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, // won't be called. mDesktopWindowDecoration.hideResizeVeil(); } - } else if (!mDisallowedAreaForEndBounds.contains((int) x, (int) y)) { - DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, - mTaskBoundsAtDragStart, mRepositionStartPoint, x, y); + } else if (DragPositioningCallbackUtility.isBelowDisallowedArea( + mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint, + y)) { + DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds, + mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y); DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(), mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer); } @@ -148,6 +154,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, mCtrlType = CTRL_TYPE_UNDEFINED; mTaskBoundsAtDragStart.setEmpty(); mRepositionStartPoint.set(0, 0); + return new Rect(mRepositionTaskBounds); } private boolean isResizing() { @@ -163,7 +170,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, startTransaction.apply(); mDesktopWindowDecoration.hideResizeVeil(); mCtrlType = CTRL_TYPE_UNDEFINED; - finishCallback.onTransitionFinished(null, null); + finishCallback.onTransitionFinished(null); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java index 9f03d9aec166..ae1a3d914be3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java @@ -22,6 +22,7 @@ import android.view.SurfaceControl; import android.window.TransitionInfo; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.splitscreen.SplitScreenController; /** * The interface used by some {@link com.android.wm.shell.ShellTaskOrganizer.TaskListener} to help @@ -39,6 +40,11 @@ public interface WindowDecorViewModel { void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter); /** + * Sets the {@link SplitScreenController} if available. + */ + void setSplitScreenController(SplitScreenController splitScreenController); + + /** * Creates a window decoration for the given task. Can be {@code null} for Fullscreen tasks but * not Freeform ones. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index ac5ff2075901..a26927586b61 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -17,6 +17,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; @@ -64,6 +65,24 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> implements AutoCloseable { /** + * The Z-order of {@link #mCaptionContainerSurface}. + * <p> + * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by + * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input + * prior to caption view itself, treating corner inputs as resize events rather than + * repositioning. + */ + static final int CAPTION_LAYER_Z_ORDER = -1; + /** + * The Z-order of the task input sink in {@link DragPositioningCallback}. + * <p> + * This task input sink is used to prevent undesired dispatching of motion events out of task + * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle + * input events first. + */ + static final int INPUT_SINK_Z_ORDER = -2; + + /** * System-wide context. Only used to create context with overridden configurations. */ final Context mContext; @@ -97,6 +116,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> SurfaceControl mCaptionContainerSurface; private WindowlessWindowManager mCaptionWindowManager; private SurfaceControlViewHost mViewHost; + private Configuration mWindowDecorConfig; private final Binder mOwner = new Binder(); private final Rect mCaptionInsetsRect = new Rect(); @@ -107,8 +127,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, - SurfaceControl taskSurface) { - this(context, displayController, taskOrganizer, taskInfo, taskSurface, + SurfaceControl taskSurface, + Configuration windowDecorConfig) { + this(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {}); } @@ -119,6 +140,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, SurfaceControl taskSurface, + Configuration windowDecorConfig, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, @@ -134,17 +156,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mSurfaceControlViewHostFactory = surfaceControlViewHostFactory; mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); - mDecorWindowContext = mContext.createConfigurationContext( - getConfigurationWithOverrides(mTaskInfo)); - } - - /** - * Get {@link Configuration} from supplied {@link RunningTaskInfo}. - * - * Allows values to be overridden before returning the configuration. - */ - protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) { - return taskInfo.getConfiguration(); + mWindowDecorConfig = windowDecorConfig; + mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig); } /** @@ -161,7 +174,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> RelayoutResult<T> outResult) { outResult.reset(); - final Configuration oldTaskConfig = mTaskInfo.getConfiguration(); if (params.mRunningTaskInfo != null) { mTaskInfo = params.mRunningTaskInfo; } @@ -180,8 +192,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> outResult.mRootView = rootView; rootView = null; // Clear it just in case we use it accidentally - final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo); - if (oldTaskConfig.densityDpi != taskConfig.densityDpi + + final int oldDensityDpi = mWindowDecorConfig.densityDpi; + mWindowDecorConfig = params.mWindowDecorConfig != null ? params.mWindowDecorConfig + : mTaskInfo.getConfiguration(); + if (oldDensityDpi != mWindowDecorConfig.densityDpi || mDisplay == null || mDisplay.getDisplayId() != mTaskInfo.displayId || oldLayoutResId != mLayoutResId) { @@ -191,7 +206,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> outResult.mRootView = null; return; } - mDecorWindowContext = mContext.createConfigurationContext(taskConfig); + mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig); if (params.mLayoutResId != 0) { outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) .inflate(params.mLayoutResId, null); @@ -204,7 +219,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } final Resources resources = mDecorWindowContext.getResources(); + final Configuration taskConfig = mTaskInfo.getConfiguration(); final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); + final boolean isFullscreen = taskConfig.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_FULLSCREEN; outResult.mWidth = taskBounds.width(); outResult.mHeight = taskBounds.height(); @@ -237,7 +255,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); final int captionWidth = taskBounds.width(); + startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) + .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER) .show(mCaptionContainerSurface); if (ViewRootImpl.CAPTION_ON_SHELL) { @@ -248,6 +268,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY; wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); + wct.addInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(), + mCaptionInsetsRect); } else { startT.hide(mCaptionContainerSurface); } @@ -259,12 +282,24 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; final Point taskPosition = mTaskInfo.positionInParent; - startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight) - .setShadowRadius(mTaskSurface, shadowRadius) + if (isFullscreen) { + // Setting the task crop to the width/height stops input events from being sent to + // some regions of the app window. See b/300324920 + // TODO(b/296921174): investigate whether crop/position needs to be set by window + // decorations at all when transition handlers are already taking ownership of the task + // surface placement/crop, especially when in fullscreen where tasks cannot be + // drag-resized by the window decoration. + startT.setWindowCrop(mTaskSurface, null); + finishT.setWindowCrop(mTaskSurface, null); + } else { + startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); + finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); + } + startT.setShadowRadius(mTaskSurface, shadowRadius) .setColor(mTaskSurface, mTmpColor) .show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) - .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); + .setShadowRadius(mTaskSurface, shadowRadius); if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { startT.setCornerRadius(mTaskSurface, params.mCornerRadius); finishT.setCornerRadius(mTaskSurface, params.mCornerRadius); @@ -301,6 +336,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } + int getCaptionHeightId() { + return Resources.ID_NULL; + } + /** * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or * registers {@link #mOnDisplaysChangedListener} if it doesn't. @@ -345,6 +384,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get(); wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar()); + wct.removeInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures()); mTaskOrganizer.applyTransaction(wct); } @@ -413,6 +454,21 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mSurfaceControlTransactionSupplier); } + /** + * Adds caption inset source to a WCT + */ + public void addCaptionInset(WindowContainerTransaction wct) { + final int captionHeightId = getCaptionHeightId(); + if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) { + return; + } + + final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId); + final Rect captionInsets = new Rect(0, 0, 0, captionHeight); + wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(), + captionInsets); + } + static class RelayoutParams { RunningTaskInfo mRunningTaskInfo; int mLayoutResId; @@ -425,6 +481,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mCaptionX; int mCaptionY; + Configuration mWindowDecorConfig; + boolean mApplyStartTransactionOnDraw; void reset() { @@ -439,6 +497,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mCaptionY = 0; mApplyStartTransactionOnDraw = false; + mWindowDecorConfig = null; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt index b67acd5c15bb..6b59ccec5148 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt @@ -5,6 +5,7 @@ import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.view.View +import android.view.View.OnLongClickListener import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView @@ -19,17 +20,19 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( rootView: View, onCaptionTouchListener: View.OnTouchListener, onCaptionButtonClickListener: View.OnClickListener, + onLongClickListener: OnLongClickListener, appName: CharSequence, appIcon: Drawable ) : DesktopModeWindowDecorationViewHolder(rootView) { - private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption) - private val captionHandle: View = rootView.findViewById(R.id.caption_handle) - private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button) - private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window) - private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button) - private val appNameTextView: TextView = rootView.findViewById(R.id.application_name) - private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon) + private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption) + private val captionHandle: View = rootView.requireViewById(R.id.caption_handle) + private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button) + private val closeWindowButton: ImageButton = rootView.requireViewById(R.id.close_window) + private val expandMenuButton: ImageButton = rootView.requireViewById(R.id.expand_menu_button) + private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window) + private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name) + private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon) init { captionView.setOnTouchListener(onCaptionTouchListener) @@ -37,6 +40,8 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( openMenuButton.setOnClickListener(onCaptionButtonClickListener) openMenuButton.setOnTouchListener(onCaptionTouchListener) closeWindowButton.setOnClickListener(onCaptionButtonClickListener) + maximizeWindowButton.setOnClickListener(onCaptionButtonClickListener) + maximizeWindowButton.onLongClickListener = onLongClickListener closeWindowButton.setOnTouchListener(onCaptionTouchListener) appNameTextView.text = appName appIconImageView.setImageDrawable(appIcon) @@ -45,10 +50,14 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( override fun bindData(taskInfo: RunningTaskInfo) { val captionDrawable = captionView.background as GradientDrawable - captionDrawable.setColor(taskInfo.taskDescription.statusBarColor) + taskInfo.taskDescription?.statusBarColor?.let { + captionDrawable.setColor(it) + } closeWindowButton.imageTintList = ColorStateList.valueOf( getCaptionCloseButtonColor(taskInfo)) + maximizeWindowButton.imageTintList = ColorStateList.valueOf( + getCaptionMaximizeButtonColor(taskInfo)) expandMenuButton.imageTintList = ColorStateList.valueOf( getCaptionExpandButtonColor(taskInfo)) appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo)) @@ -70,6 +79,14 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( } } + private fun getCaptionMaximizeButtonColor(taskInfo: RunningTaskInfo): Int { + return if (shouldUseLightCaptionColors(taskInfo)) { + context.getColor(R.color.desktop_mode_caption_maximize_button_light) + } else { + context.getColor(R.color.desktop_mode_caption_maximize_button_dark) + } + } + private fun getCaptionExpandButtonColor(taskInfo: RunningTaskInfo): Int { return if (shouldUseLightCaptionColors(taskInfo)) { context.getColor(R.color.desktop_mode_caption_expand_button_light) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt index 47a12a0cb71c..9374ac95e83d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt @@ -17,8 +17,8 @@ internal class DesktopModeFocusedWindowDecorationViewHolder( onCaptionButtonClickListener: View.OnClickListener ) : DesktopModeWindowDecorationViewHolder(rootView) { - private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption) - private val captionHandle: ImageButton = rootView.findViewById(R.id.caption_handle) + private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption) + private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle) init { captionView.setOnTouchListener(onCaptionTouchListener) @@ -27,9 +27,10 @@ internal class DesktopModeFocusedWindowDecorationViewHolder( } override fun bindData(taskInfo: RunningTaskInfo) { - val captionColor = taskInfo.taskDescription.statusBarColor - val captionDrawable = captionView.background as GradientDrawable - captionDrawable.setColor(captionColor) + taskInfo.taskDescription?.statusBarColor?.let { captionColor -> + val captionDrawable = captionView.background as GradientDrawable + captionDrawable.setColor(captionColor) + } captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo)) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt index 514ea52cb8ae..49e8d15dcc02 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt @@ -1,28 +1,38 @@ package com.android.wm.shell.windowdecor.viewholder import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context import android.graphics.Color import android.view.View +import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS /** * Encapsulates the root [View] of a window decoration and its children to facilitate looking up * children (via findViewById) and updating to the latest data from [RunningTaskInfo]. */ internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) { - val context: Context = rootView.context + val context: Context = rootView.context - /** - * A signal to the view holder that new data is available and that the views should be updated - * to reflect it. - */ - abstract fun bindData(taskInfo: RunningTaskInfo) + /** + * A signal to the view holder that new data is available and that the views should be updated to + * reflect it. + */ + abstract fun bindData(taskInfo: RunningTaskInfo) - /** - * Whether the caption items should use the 'light' color variant so that there's good contrast - * with the caption background color. - */ - protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean { - return Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5 - } + /** + * Whether the caption items should use the 'light' color variant so that there's good contrast + * with the caption background color. + */ + protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean { + return taskInfo.taskDescription + ?.let { taskDescription -> + if (Color.alpha(taskDescription.statusBarColor) != 0 && + taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5 + } else { + taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0 + } + } ?: false + } } diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index b6696c70dbb1..6fd3354700f7 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -23,14 +23,106 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -android_test { - name: "WMShellFlickerTests", +filegroup { + name: "WMShellFlickerTestsUtils-src", + srcs: ["src/com/android/wm/shell/flicker/utils/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsBase-src", + srcs: ["src/com/android/wm/shell/flicker/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsBubbles-src", + srcs: ["src/com/android/wm/shell/flicker/bubble/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsPip-src", + srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsPipCommon-src", + srcs: ["src/com/android/wm/shell/flicker/pip/common/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsPipApps-src", + srcs: ["src/com/android/wm/shell/flicker/pip/apps/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsSplitScreenBase-src", srcs: [ - "src/**/*.java", - "src/**/*.kt", + "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt", + ], +} + +filegroup { + name: "WMShellFlickerTestsSplitScreenGroup1-src", + srcs: [ + "src/com/android/wm/shell/flicker/splitscreen/A*.kt", + "src/com/android/wm/shell/flicker/splitscreen/B*.kt", + "src/com/android/wm/shell/flicker/splitscreen/C*.kt", + "src/com/android/wm/shell/flicker/splitscreen/D*.kt", + "src/com/android/wm/shell/flicker/splitscreen/E*.kt", + ], +} + +filegroup { + name: "WMShellFlickerTestsSplitScreenGroup2-src", + srcs: [ + "src/com/android/wm/shell/flicker/splitscreen/*.kt", + ], +} + +filegroup { + name: "WMShellFlickerServicePlatinumTests-src", + srcs: [ + "src/com/android/wm/shell/flicker/service/*/platinum/**/*.kt", + "src/com/android/wm/shell/flicker/service/*/scenarios/**/*.kt", + "src/com/android/wm/shell/flicker/service/common/**/*.kt", ], - manifest: "AndroidManifest.xml", - test_config: "AndroidTest.xml", +} + +filegroup { + name: "WMShellFlickerServiceTests-src", + srcs: [ + "src/com/android/wm/shell/flicker/service/**/*.kt", + ], + exclude_srcs: [ + "src/com/android/wm/shell/flicker/service/*/platinum/**/*.kt", + ], +} + +java_library { + name: "wm-shell-flicker-utils", + platform_apis: true, + optimize: { + enabled: false, + }, + srcs: [ + ":WMShellFlickerTestsUtils-src", + ], + static_libs: [ + "androidx.test.ext.junit", + "flickertestapplib", + "flickerlib", + "flickerlib-helpers", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", + "launcher-helper-lib", + "launcher-aosp-tapl", + ], +} + +java_defaults { + name: "WMShellFlickerTestsDefault", + manifest: "manifests/AndroidManifest.xml", + test_config_template: "AndroidTestTemplate.xml", platform_apis: true, certificate: "platform", optimize: { @@ -39,17 +131,134 @@ android_test { test_suites: ["device-tests"], libs: ["android.test.runner"], static_libs: [ + "wm-shell-flicker-utils", "androidx.test.ext.junit", + "flickertestapplib", "flickerlib", - "flickerlib-apphelpers", "flickerlib-helpers", - "truth-prebuilt", - "app-helpers-core", + "flickerlib-trace_processor_shell", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", "launcher-helper-lib", "launcher-aosp-tapl", - "wm-flicker-common-assertions", - "wm-flicker-common-app-helpers", - "platform-test-annotations", - "flickertestapplib", + ], + data: [ + ":FlickerTestApp", + "trace_config/*", + ], +} + +android_test { + name: "WMShellFlickerTestsOther", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestOther.xml"], + package_name: "com.android.wm.shell.flicker", + instrumentation_target_package: "com.android.wm.shell.flicker", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + exclude_srcs: [ + ":WMShellFlickerTestsBubbles-src", + ":WMShellFlickerTestsPip-src", + ":WMShellFlickerTestsPipCommon-src", + ":WMShellFlickerTestsPipApps-src", + ":WMShellFlickerTestsSplitScreenGroup1-src", + ":WMShellFlickerTestsSplitScreenGroup2-src", + ":WMShellFlickerTestsSplitScreenBase-src", + ":WMShellFlickerServiceTests-src", + ":WMShellFlickerServicePlatinumTests-src", + ], +} + +android_test { + name: "WMShellFlickerTestsBubbles", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestBubbles.xml"], + package_name: "com.android.wm.shell.flicker.bubbles", + instrumentation_target_package: "com.android.wm.shell.flicker.bubbles", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsBubbles-src", + ], +} + +android_test { + name: "WMShellFlickerTestsPip", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestPip.xml"], + package_name: "com.android.wm.shell.flicker.pip", + instrumentation_target_package: "com.android.wm.shell.flicker.pip", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsPip-src", + ":WMShellFlickerTestsPipCommon-src", + ], +} + +android_test { + name: "WMShellFlickerTestsPipApps", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestPip.xml"], + package_name: "com.android.wm.shell.flicker.pip.apps", + instrumentation_target_package: "com.android.wm.shell.flicker.pip.apps", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsPipApps-src", + ":WMShellFlickerTestsPipCommon-src", + ], +} + +android_test { + name: "WMShellFlickerTestsSplitScreenGroup1", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"], + package_name: "com.android.wm.shell.flicker.splitscreen", + instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsSplitScreenBase-src", + ":WMShellFlickerTestsSplitScreenGroup1-src", + ], +} + +android_test { + name: "WMShellFlickerTestsSplitScreenGroup2", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"], + package_name: "com.android.wm.shell.flicker.splitscreen", + instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsSplitScreenBase-src", + ":WMShellFlickerTestsSplitScreenGroup2-src", + ], + exclude_srcs: [ + ":WMShellFlickerTestsSplitScreenGroup1-src", + ], +} + +android_test { + name: "WMShellFlickerServiceTests", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestService.xml"], + package_name: "com.android.wm.shell.flicker.service", + instrumentation_target_package: "com.android.wm.shell.flicker.service", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerServiceTests-src", + ], +} + +android_test { + name: "WMShellFlickerServicePlatinumTests", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestService.xml"], + package_name: "com.android.wm.shell.flicker.service", + instrumentation_target_package: "com.android.wm.shell.flicker.service", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerServicePlatinumTests-src", ], } diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml deleted file mode 100644 index b5937ae80f0a..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright 2020 Google Inc. All Rights Reserved. - --> -<configuration description="Runs WindowManager Shell Flicker Tests"> - <option name="test-tag" value="FlickerTests" /> - <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> - <!-- keeps the screen on during tests --> - <option name="screen-always-on" value="on" /> - <!-- prevents the phone from restarting --> - <option name="force-skip-system-props" value="true" /> - <!-- set WM tracing verbose level to all --> - <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" /> - <!-- Increase trace size: 20mb for WM and 80mb for SF --> - <option name="run-command" value="cmd window tracing size 20480" /> - <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" /> - </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" /> - <option name="run-command" value="settings put system show_touches 1" /> - <option name="run-command" value="settings put system pointer_location 1" /> - <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> - <option name="teardown-command" value="settings delete system show_touches" /> - <option name="teardown-command" value="settings delete system pointer_location" /> - <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" /> - </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="FlickerTestApp.apk" /> - </target_preparer> - <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="com.android.wm.shell.flicker"/> - <option name="shell-timeout" value="6600s" /> - <option name="test-timeout" value="6000s" /> - <option name="hidden-api-checks" value="false" /> - </test> - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="directory-keys" value="/sdcard/flicker" /> - <option name="collect-on-run-ended-only" value="true" /> - <option name="clean-up" value="true" /> - </metrics_collector> -</configuration> diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml new file mode 100644 index 000000000000..b13e9a248575 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}"> + <option name="test-tag" value="FlickerTests"/> + <!-- Needed for storing the perfetto trace files in the sdcard/test_results--> + <option name="isolated-storage" value="false"/> + + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- keeps the screen on during tests --> + <option name="screen-always-on" value="on"/> + <!-- prevents the phone from restarting --> + <option name="force-skip-system-props" value="true"/> + <!-- set WM tracing verbose level to all --> + <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"/> + <!-- Increase trace size: 20mb for WM and 80mb for SF --> + <option name="run-command" value="cmd window tracing size 20480"/> + <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="test-user-token" value="%TEST_USER%"/> + <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> + <option name="run-command" value="settings put system show_touches 1"/> + <option name="run-command" value="settings put system pointer_location 1"/> + <option name="teardown-command" + value="settings delete secure show_ime_with_hard_keyboard"/> + <option name="teardown-command" value="settings delete system show_touches"/> + <option name="teardown-command" value="settings delete system pointer_location"/> + <option name="teardown-command" + value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true"/> + <option name="test-file-name" value="{MODULE}.apk"/> + <option name="test-file-name" value="FlickerTestApp.apk"/> + </target_preparer> + <!-- Enable mocking GPS location by the test app --> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" + value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/> + <option name="teardown-command" + value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/> + </target_preparer> + + <!-- Needed for pushing the trace config file --> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="push-file" + key="trace_config.textproto" + value="/data/misc/perfetto-traces/trace_config.textproto" + /> + <!--Install the content provider automatically when we push some file in sdcard folder.--> + <!--Needed to avoid the installation during the test suite.--> + <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="{PACKAGE}"/> + <option name="shell-timeout" value="6600s"/> + <option name="test-timeout" value="6000s"/> + <option name="hidden-api-checks" value="false"/> + <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed + <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> + --> + <!-- PerfettoListener related arguments --> + <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/> + <option name="instrumentation-arg" + key="perfetto_config_file" + value="trace_config.textproto" + /> + <option name="instrumentation-arg" key="per_run" value="true"/> + </test> + <!-- Needed for pulling the collected trace config on to the host --> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="pull-pattern-keys" value="perfetto_file_path"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell.flicker/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell.flicker.pip/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell.flicker.service/files"/> + <option name="collect-on-run-ended-only" value="true"/> + <option name="clean-up" value="true"/> + </metrics_collector> +</configuration> diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml index 4721741611cf..ae130b8f6f7d 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml @@ -15,6 +15,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" package="com.android.wm.shell.flicker"> <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/> @@ -44,9 +45,13 @@ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> <!-- Enable bubble notification--> <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> + <!-- Allow the test to connect to perfetto trace processor --> + <uses-permission android:name="android.permission.INTERNET"/> - <!-- Allow the test to write directly to /sdcard/ --> - <application android:requestLegacyExternalStorage="true" android:largeHeap="true"> + <!-- Allow the test to write directly to /sdcard/ and connect to trace processor --> + <application android:requestLegacyExternalStorage="true" + android:networkSecurityConfig="@xml/network_security_config" + android:largeHeap="true"> <uses-library android:name="android.test.runner"/> <service android:name=".NotificationListener" @@ -57,10 +62,11 @@ <action android:name="android.service.notification.NotificationListenerService" /> </intent-filter> </service> - </application> - <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.wm.shell.flicker" - android:label="WindowManager Shell Flicker Tests"> - </instrumentation> + <!-- (b/197936012) Remove startup provider due to test timeout issue --> + <provider + android:name="androidx.startup.InitializationProvider" + android:authorities="${applicationId}.androidx-startup" + tools:node="remove" /> + </application> </manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml new file mode 100644 index 000000000000..437871f1bb8f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.bubble"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.bubble" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml new file mode 100644 index 000000000000..cf642f63a41d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml new file mode 100644 index 000000000000..fa42a4570b7d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml @@ -0,0 +1,27 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.pip"> + + <!-- Enable mocking GPS location --> + <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.pip" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml new file mode 100644 index 000000000000..c7aca1a72696 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.service"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.service" + android:label="WindowManager Flicker Service Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml new file mode 100644 index 000000000000..887d8db3042f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.splitscreen"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.splitscreen" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/flicker/res/xml/network_security_config.xml new file mode 100644 index 000000000000..4bd9ca049f55 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/res/xml/network_security_config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<network-security-config> + <domain-config cleartextTrafficPermitted="true"> + <domain includeSubdomains="true">localhost</domain> + </domain-config> +</network-security-config> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt index e06e074ee98a..0f3e0f5ef043 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt @@ -19,14 +19,14 @@ package com.android.wm.shell.flicker import android.app.Instrumentation import android.tools.device.flicker.junit.FlickerBuilderProvider import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.tapl.LauncherInstrumentation abstract class BaseBenchmarkTest @JvmOverloads constructor( - protected open val flicker: FlickerTest, + protected open val flicker: LegacyFlickerTest, protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(), protected val tapl: LauncherInstrumentation = LauncherInstrumentation() ) { 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 index c98c5a0ad1a6..735fbfb341f5 100644 --- 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 @@ -18,9 +18,10 @@ package com.android.wm.shell.flicker import android.app.Instrumentation import android.tools.common.traces.component.ComponentNameMatcher -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.utils.ICommonAssertions /** * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR], @@ -30,7 +31,7 @@ import com.android.launcher3.tapl.LauncherInstrumentation abstract class BaseTest @JvmOverloads constructor( - override val flicker: FlickerTest, + override val flicker: LegacyFlickerTest, instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(), tapl: LauncherInstrumentation = LauncherInstrumentation() ) : BaseBenchmarkTest(flicker, instrumentation, tapl), ICommonAssertions diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt index 61781565270b..77f14f1b66a3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt @@ -17,28 +17,26 @@ package com.android.wm.shell.flicker.appcompat import android.content.Context -import android.system.helpers.CommandsHelper import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import com.android.server.wm.flicker.helpers.setRotation +import android.tools.device.flicker.legacy.FlickerTestData +import android.tools.device.flicker.legacy.LegacyFlickerTest import com.android.server.wm.flicker.helpers.LetterboxAppHelper -import android.tools.device.flicker.legacy.FlickerTestFactory -import android.tools.device.flicker.legacy.IFlickerTestData +import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.BaseTest -import com.android.wm.shell.flicker.appWindowIsVisibleAtStart -import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.layerKeepVisible -import org.junit.After +import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtStart +import com.android.wm.shell.flicker.utils.appWindowKeepVisible +import com.android.wm.shell.flicker.utils.layerKeepVisible import org.junit.Assume import org.junit.Before -import org.junit.runners.Parameterized +import org.junit.Rule -abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) { +abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { protected val context: Context = instrumentation.context protected val letterboxApp = LetterboxAppHelper(instrumentation) - lateinit var cmdHelper: CommandsHelper - private lateinit var letterboxStyle: HashMap<String, String> + + @JvmField @Rule val letterboxRule: LetterboxRule = LetterboxRule() /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -48,62 +46,17 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) { letterboxApp.launchViaIntent(wmHelper) setEndRotation() } - teardown { - letterboxApp.exit(wmHelper) - } + teardown { letterboxApp.exit(wmHelper) } } @Before fun before() { - cmdHelper = CommandsHelper.getInstance(instrumentation) - Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest()) - letterboxStyle = mapLetterboxStyle() - setLetterboxEducationEnabled(false) - } - - @After - fun after() { - resetLetterboxEducationEnabled() - } - - private fun mapLetterboxStyle(): HashMap<String, String> { - val res = cmdHelper.executeShellCommand("wm get-letterbox-style") - val lines = res.lines() - val map = HashMap<String, String>() - for (line in lines) { - val keyValuePair = line.split(":") - if (keyValuePair.size == 2) { - val key = keyValuePair[0].trim() - map[key] = keyValuePair[1].trim() - } - } - return map - } - - private fun getLetterboxStyle(): HashMap<String, String> { - if (!::letterboxStyle.isInitialized) { - letterboxStyle = mapLetterboxStyle() - } - return letterboxStyle - } - - private fun resetLetterboxEducationEnabled() { - val enabled = getLetterboxStyle().getValue("Is education enabled") - cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled") - } - - private fun setLetterboxEducationEnabled(enabled: Boolean) { - cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled") - } - - private fun isIgnoreOrientationRequest(): Boolean { - val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request") - return res != null && res.contains("true") + Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest) } - fun IFlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation) + fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation) - fun IFlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation) + fun FlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation) /** Checks that app entering letterboxed state have rounded corners */ fun assertLetterboxAppAtStartHasRoundedCorners() { @@ -118,7 +71,7 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) { /** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */ private fun assumeLetterboxRoundedCornersEnabled() { - Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0") + Assume.assumeTrue(letterboxRule.hasCornerRadius) } fun assertLetterboxAppVisibleAtStartAndEnd() { @@ -126,25 +79,21 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) { flicker.appWindowIsVisibleAtEnd(letterboxApp) } + fun assertLetterboxAppKeepVisible() { + assertLetterboxAppWindowKeepVisible() + assertLetterboxAppLayerKeepVisible() + } + fun assertAppLetterboxedAtEnd() = - flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) } + flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) } fun assertAppLetterboxedAtStart() = - flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) } + flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) } + + fun assertAppStaysLetterboxed() = + flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) } fun assertLetterboxAppLayerKeepVisible() = flicker.layerKeepVisible(letterboxApp) - companion object { - /** - * Creates the test configurations. - * - * See [FlickerTestFactory.rotationTests] for configuring screen orientation and - * navigation modes. - */ - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.rotationTests() - } - } + fun assertLetterboxAppWindowKeepVisible() = flicker.appWindowKeepVisible(letterboxApp) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt new file mode 100644 index 000000000000..744e8c2eb06f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt @@ -0,0 +1,106 @@ +/* + * 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.appcompat + +import android.app.Instrumentation +import android.system.helpers.CommandsHelper +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** JUnit Rule to handle letterboxStyles and states */ +class LetterboxRule( + private val withLetterboxEducationEnabled: Boolean = false, + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(), + private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation) +) : TestRule { + + private val execAdb: (String) -> String = { cmd -> cmdHelper.executeShellCommand(cmd) } + private lateinit var _letterboxStyle: MutableMap<String, String> + + val letterboxStyle: Map<String, String> + get() { + if (!::_letterboxStyle.isInitialized) { + _letterboxStyle = mapLetterboxStyle() + } + return _letterboxStyle + } + + val cornerRadius: Int? + get() = asInt(letterboxStyle["Corner radius"]) + + val hasCornerRadius: Boolean + get() { + val radius = cornerRadius + return radius != null && radius > 0 + } + + val isIgnoreOrientationRequest: Boolean + get() = execAdb("wm get-ignore-orientation-request")?.contains("true") ?: false + + override fun apply(base: Statement?, description: Description?): Statement { + resetLetterboxStyle() + _letterboxStyle = mapLetterboxStyle() + val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled") + var hasLetterboxEducationStateChanged = false + if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) { + hasLetterboxEducationStateChanged = true + execAdb("wm set-letterbox-style --isEducationEnabled " + withLetterboxEducationEnabled) + } + return try { + object : Statement() { + @Throws(Throwable::class) + override fun evaluate() { + base!!.evaluate() + } + } + } finally { + if (hasLetterboxEducationStateChanged) { + execAdb( + "wm set-letterbox-style --isEducationEnabled " + isLetterboxEducationEnabled + ) + } + resetLetterboxStyle() + } + } + + private fun mapLetterboxStyle(): HashMap<String, String> { + val res = execAdb("wm get-letterbox-style") + val lines = res.lines() + val map = HashMap<String, String>() + for (line in lines) { + val keyValuePair = line.split(":") + if (keyValuePair.size == 2) { + val key = keyValuePair[0].trim() + map[key] = keyValuePair[1].trim() + } + } + return map + } + + private fun resetLetterboxStyle() { + execAdb("wm reset-letterbox-style") + } + + private fun asInt(str: String?): Int? = + try { + str?.toInt() + } catch (e: NumberFormatException) { + null + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt index c2141a370f10..1e5e42fb077e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt @@ -17,10 +17,12 @@ package com.android.wm.shell.flicker.appcompat import android.platform.test.annotations.Postsubmit +import android.tools.common.flicker.assertions.FlickerTest import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import org.junit.Test import org.junit.runner.RunWith @@ -29,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test launching app in size compat mode. * - * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest` + * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest` * * Actions: * ``` @@ -45,7 +47,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) { +class OpenAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -64,15 +66,14 @@ class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) */ @Postsubmit @Test - fun letterboxAppFocusedAtEnd() = flicker.assertEventLog { focusChanges(letterboxApp.`package`) } + fun letterboxAppFocusedAtEnd() = + flicker.assertEventLog { focusChanges(letterboxApp.packageName) } @Postsubmit @Test fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners() - @Postsubmit - @Test - fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd() + @Postsubmit @Test fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd() /** * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't @@ -90,4 +91,18 @@ class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) .isInvisible(ComponentNameMatcher.ROTATION) } } + + companion object { + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.rotationTests() + } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt new file mode 100644 index 000000000000..2fa1ec386781 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt @@ -0,0 +1,117 @@ +/* + * 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.appcompat + +import android.platform.test.annotations.Postsubmit +import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.RequiresDevice +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching app in size compat mode. + * + * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest` + * + * Actions: + * ``` + * Launch a letteboxed app and then a transparent activity from it. We test the bounds + * are the same. + * ``` + * + * Notes: + * ``` + * Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [BaseTest] + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseAppCompat(flicker) { + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit + get() = { + setup { letterboxTranslucentLauncherApp.launchViaIntent(wmHelper) } + transitions { + waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found") + } + teardown { + letterboxTranslucentApp.exit(wmHelper) + letterboxTranslucentLauncherApp.exit(wmHelper) + } + } + + /** Checks the transparent activity is launched on top of the opaque one */ + @Postsubmit + @Test + fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() { + flicker.assertWm { + this.isAppWindowOnTop(letterboxTranslucentLauncherApp) + .then() + .isAppWindowOnTop(letterboxTranslucentApp) + } + } + + /** Checks that the activity is letterboxed */ + @Postsubmit + @Test + fun translucentActivityIsLetterboxed() { + flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) } + } + + /** Checks that the translucent activity inherits bounds from the opaque one. */ + @Postsubmit + @Test + fun translucentActivityInheritsBoundsFromOpaqueActivity() { + flicker.assertLayersEnd { + this.visibleRegion(letterboxTranslucentApp) + .coversExactly(visibleRegion(letterboxTranslucentLauncherApp).region) + } + } + + /** Checks that the translucent activity has rounded corners */ + @Postsubmit + @Test + fun translucentActivityHasRoundedCorners() { + flicker.assertLayersEnd { this.hasRoundedCorners(letterboxTranslucentApp) } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation + * modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_90) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt new file mode 100644 index 000000000000..b74aa1d7bf73 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt @@ -0,0 +1,274 @@ +/* + * 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.appcompat + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.datatypes.Rect +import android.tools.common.flicker.assertions.FlickerTest +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +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 switching to letterboxed app from launcher + * + * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest` + * + * Actions: + * ``` + * Launch a letterboxed app + * Navigate home to show launcher + * Swipe right from the bottom of the screen to quick switch back to the app + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) { + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + + letterboxApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withHomeActivityVisible() + .withWindowSurfaceDisappeared(letterboxApp) + .waitForAndVerify() + + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + } + transitions { + tapl.workspace.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(letterboxApp) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + teardown { letterboxApp.exit(wmHelper) } + } + + /** + * Checks that [letterboxApp] is the top window at the end of the transition once we have fully + * quick switched from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun endsWithAppBeingOnTop() { + flicker.assertWmEnd { this.isAppWindowOnTop(letterboxApp) } + } + + /** Checks that the transition starts with the home activity being tagged as visible. */ + @Postsubmit + @Test + fun startsWithHomeActivityFlaggedVisible() { + flicker.assertWmStart { this.isHomeActivityVisible() } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows + * filling/covering exactly display size + */ + @Postsubmit + @Test + fun startsWithLauncherWindowsCoverFullScreen() { + flicker.assertWmStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers + * filling/covering exactly the display size. + */ + @Postsubmit + @Test + fun startsWithLauncherLayersCoverFullScreen() { + flicker.assertLayersStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top + * window. + */ + @Postsubmit + @Test + fun startsWithLauncherBeingOnTop() { + flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) } + } + + /** + * Checks that the transition ends with the home activity being flagged as not visible. By this + * point we should have quick switched away from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun endsWithHomeActivityFlaggedInvisible() { + flicker.assertWmEnd { this.isHomeActivityInvisible() } + } + + /** + * Checks that [letterboxApp]'s window starts off invisible and becomes visible at some point + * before the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun appWindowBecomesAndStaysVisible() { + flicker.assertWm { + this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp) + } + } + + /** + * Checks that [letterboxApp]'s layer starts off invisible and becomes visible at some point + * before the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun appLayerBecomesAndStaysVisible() { + flicker.assertLayers { this.isInvisible(letterboxApp).then().isVisible(letterboxApp) } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes + * invisible at some point before the end of the transition and then stays invisible until the + * end of the transition. + */ + @Postsubmit + @Test + fun launcherWindowBecomesAndStaysInvisible() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes + * invisible at some point before the end of the transition and then stays invisible until the + * end of the transition. + */ + @Postsubmit + @Test + fun launcherLayerBecomesAndStaysInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isInvisible(ComponentNameMatcher.LAUNCHER) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app + * window is visible. Ensures that at any point, either the launcher or [letterboxApp] windows + * are at least partially visible. + */ + @Postsubmit + @Test + fun appWindowIsVisibleOnceLauncherWindowIsInvisible() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(letterboxApp) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer + * is visible. Ensures that at any point, either the launcher or [letterboxApp] layers are at + * least partially visible. + */ + @Postsubmit + @Test + fun appLayerIsVisibleOnceLauncherLayerIsInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(letterboxApp) + } + } + + /** + * Checks that the [ComponentNameMatcher.LETTERBOX] layer is visible as soon as the + * [letterboxApp] layer is visible at the end of the transition once we have fully quick + * switched from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun appAndLetterboxLayersBothVisibleOnceLauncherIsInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(letterboxApp) + .isVisible(ComponentNameMatcher.LETTERBOX) + } + } + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + supportedRotations = listOf(Rotation.ROTATION_90) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt new file mode 100644 index 000000000000..68fa8d2fc2e8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt @@ -0,0 +1,99 @@ +/* + * 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.appcompat + +import android.platform.test.annotations.Postsubmit +import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import android.tools.device.helpers.WindowUtils +import androidx.test.filters.RequiresDevice +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching a fixed portrait letterboxed app in landscape and repositioning to the right. + * + * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest` + * + * Actions: + * + * ``` + * Launch a fixed portrait app in landscape to letterbox app + * Double tap to the right to reposition app and wait for app to move + * ``` + * + * Notes: + * + * ``` + * Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [BaseTest] + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +class RepositionFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) { + + val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation).bounds + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit + get() = { + setup { + setStartRotation() + letterboxApp.launchViaIntent(wmHelper) + } + transitions { + letterboxApp.repositionHorizontally(displayBounds, true) + letterboxApp.waitForAppToMoveHorizontallyTo(wmHelper, displayBounds, true) + } + teardown { + letterboxApp.repositionHorizontally(displayBounds, false) + letterboxApp.exit(wmHelper) + } + } + + @Postsubmit + @Test + fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners() + + @Postsubmit @Test fun letterboxAppLayerKeepVisible() = assertLetterboxAppLayerKeepVisible() + + @Postsubmit @Test fun appStaysLetterboxed() = assertAppStaysLetterboxed() + + @Postsubmit @Test fun appKeepVisible() = assertLetterboxAppKeepVisible() + + companion object { + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_90) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt index b0e1a42306df..fcb6931af9a2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt @@ -17,9 +17,11 @@ package com.android.wm.shell.flicker.appcompat import android.platform.test.annotations.Postsubmit +import android.tools.common.flicker.assertions.FlickerTest import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice import org.junit.Test @@ -29,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test restarting app in size compat mode. * - * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest` + * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest` * * Actions: * ``` @@ -46,7 +48,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) { +class RestartAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -69,13 +71,9 @@ class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flick } } - @Postsubmit - @Test - fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible() + @Postsubmit @Test fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible() - @Postsubmit - @Test - fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart() + @Postsubmit @Test fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart() @Postsubmit @Test @@ -88,4 +86,18 @@ class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flick val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation) flicker.assertLayersEnd { visibleRegion(letterboxApp).coversAtMost(displayBounds) } } + + companion object { + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.rotationTests() + } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt new file mode 100644 index 000000000000..9792c859cced --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt @@ -0,0 +1,60 @@ +/* + * 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.appcompat + +import android.content.Context +import android.tools.device.flicker.legacy.FlickerTestData +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.toFlickerComponent +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.helpers.LetterboxAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.flicker.BaseTest +import org.junit.Assume +import org.junit.Before +import org.junit.Rule + +abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { + protected val context: Context = instrumentation.context + protected val letterboxTranslucentLauncherApp = + LetterboxAppHelper( + instrumentation, + launcherName = ActivityOptions.LaunchTransparentActivity.LABEL, + component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent() + ) + protected val letterboxTranslucentApp = + LetterboxAppHelper( + instrumentation, + launcherName = ActivityOptions.TransparentActivity.LABEL, + component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent() + ) + + @JvmField @Rule val letterboxRule: LetterboxRule = LetterboxRule() + + @Before + fun before() { + Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest) + } + + protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? = + device.wait(Until.findObject(By.text("Launch Transparent")), FIND_TIMEOUT) + + protected fun FlickerTestData.goBack() = device.pressBack() +} 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 bab81d79c804..bc095bbacc5a 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 @@ -23,19 +23,20 @@ import android.content.pm.PackageManager import android.os.ServiceManager import android.tools.common.Rotation import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -import android.tools.device.flicker.legacy.IFlickerTestData +import android.tools.device.flicker.legacy.FlickerTestData +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.SYSTEMUI_PACKAGE import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.LaunchBubbleHelper +import com.android.server.wm.flicker.helpers.MultiWindowUtils import com.android.wm.shell.flicker.BaseTest import org.junit.runners.Parameterized /** Base configurations for Bubble flicker tests */ -abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) { +abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker) { protected val context: Context = instrumentation.context protected val testApp = LaunchBubbleHelper(instrumentation) @@ -47,7 +48,7 @@ abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) { private val uid = context.packageManager - .getApplicationInfo(testApp.`package`, PackageManager.ApplicationInfoFlags.of(0)) + .getApplicationInfo(testApp.packageName, PackageManager.ApplicationInfoFlags.of(0)) .uid @JvmOverloads @@ -56,8 +57,11 @@ abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) { ): FlickerBuilder.() -> Unit { return { setup { + MultiWindowUtils.executeShellCommand( + instrumentation, + "settings put secure force_hide_bubbles_user_education 1") notifyManager.setBubblesAllowed( - testApp.`package`, + testApp.packageName, uid, NotificationManager.BUBBLE_PREFERENCE_ALL ) @@ -67,33 +71,39 @@ abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) { } teardown { + MultiWindowUtils.executeShellCommand( + instrumentation, + "settings put secure force_hide_bubbles_user_education 0") notifyManager.setBubblesAllowed( - testApp.`package`, + testApp.packageName, uid, NotificationManager.BUBBLE_PREFERENCE_NONE ) - testApp.exit() + device.wait( + Until.gone(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), + FIND_OBJECT_TIMEOUT + ) + testApp.exit(wmHelper) } extraSpec(this) } } - protected fun IFlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? = + protected fun FlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? = device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT) - protected fun IFlickerTestData.waitAndGetCancelAllBtn(): UiObject2? = + protected fun FlickerTestData.waitAndGetCancelAllBtn(): UiObject2? = device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT) companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } - const val FIND_OBJECT_TIMEOUT = 2000L + const val FIND_OBJECT_TIMEOUT = 4000L const val SYSTEM_UI_PACKAGE = SYSTEMUI_PACKAGE const val BUBBLE_RES_NAME = "bubble_view" } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt index 2474ecf74cf9..55039f59190b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt @@ -17,12 +17,11 @@ package com.android.wm.shell.flicker.bubble import android.os.SystemClock -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest +import androidx.test.filters.FlakyTest import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until @@ -40,11 +39,10 @@ import org.junit.runners.Parameterized * Switch in different bubble notifications * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FlakyTest(bugId = 217777115) -open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = buildTransition { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt index 8474ce0e64e5..9ca7bf113589 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt @@ -19,12 +19,13 @@ package com.android.wm.shell.flicker.bubble import android.content.Context import android.graphics.Point import android.platform.test.annotations.Presubmit +import android.tools.common.flicker.subject.layers.LayersTraceSubject +import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import android.util.DisplayMetrics import android.view.WindowManager -import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import org.junit.Test @@ -41,10 +42,9 @@ import org.junit.runners.Parameterized * Dismiss a bubble notification * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) { private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager private val displaySize = DisplayMetrics() @@ -73,4 +73,15 @@ open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScree open fun testAppIsAlwaysVisible() { flicker.assertLayers { this.isVisible(testApp) } } + + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + + listOf(testApp, ComponentNameMatcher(className = "Bubbles!#")) + ) + } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt index 889e1771593d..b007e6b3535c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt @@ -16,14 +16,14 @@ package com.android.wm.shell.flicker.bubble -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest 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 @@ -48,7 +48,8 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +class OpenActivityFromBubbleOnLocksreenTest(flicker: LegacyFlickerTest) : + BaseBubbleScreen(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -80,7 +81,7 @@ class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleSc instrumentation.uiAutomation.syncInputTransactions() val showBubble = device.wait( - Until.findObject(By.res("com.android.systemui", "bubble_view")), + Until.findObject(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT ) showBubble?.click() ?: error("Bubble notify not found") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt index 07ba41333071..4959672d865b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt @@ -19,8 +19,7 @@ package com.android.wm.shell.flicker.bubble import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import org.junit.Test @@ -39,10 +38,9 @@ import org.junit.runners.Parameterized * The activity for the bubble is launched * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class OpenActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +class OpenActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt index 29f76d01af83..0d95574aca06 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt @@ -19,8 +19,7 @@ package com.android.wm.shell.flicker.bubble import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import org.junit.Test @@ -38,10 +37,9 @@ import org.junit.runners.Parameterized * Send a bubble notification * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +class SendBubbleNotificationTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -55,6 +53,7 @@ open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(f FIND_OBJECT_TIMEOUT ) ?: error("No bubbles found") + device.waitForIdle() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt index f7ce87088040..c335d3dc7f4b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt @@ -16,26 +16,19 @@ package com.android.wm.shell.flicker.pip -import android.app.Instrumentation -import android.os.SystemClock import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation -import android.tools.device.apphelpers.StandardAppHelper import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.filters.RequiresDevice -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.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -69,17 +62,16 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) : - AutoEnterPipOnGoToHomeTest(flicker) { +class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : + AutoEnterPipOnGoToHomeTest(flicker) { private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) /** Second app used to enter split screen mode */ - protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation) - fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper = - SimpleAppHelper( - instrumentation, - ActivityOptions.SplitScreen.Primary.LABEL, - ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() - ) + private val secondAppForSplitScreen = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit @@ -88,14 +80,7 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) : secondAppForSplitScreen.launchViaIntent(wmHelper) pipApp.launchViaIntent(wmHelper) tapl.goHome() - enterSplitScreen() - // wait until split screen is established - wmHelper - .StateSyncBuilder() - .withWindowSurfaceAppeared(pipApp) - .withWindowSurfaceAppeared(secondAppForSplitScreen) - .withSplitDividerVisible() - .waitForAndVerify() + SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen) pipApp.enableAutoEnterForPipActivity() } teardown { @@ -107,46 +92,6 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) : transitions { tapl.goHome() } } - // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils - private val TIMEOUT_MS = 3_000L - private val overviewSnapshotSelector: BySelector - get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot") - private fun enterSplitScreen() { - // 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 = tapl.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. - snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> - t2.getVisibleBounds().left - t1.getVisibleBounds().left - } - snapshots.sortWith { 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) - } - @Presubmit @Test override fun pipOverlayLayerAppearThenDisappear() { @@ -190,11 +135,10 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) : 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(NavBar.MODE_GESTURAL) + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } 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 b95732e43357..e38c4c34fc53 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 @@ -19,7 +19,8 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import org.junit.Assume import org.junit.FixMethodOrder @@ -53,10 +54,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) { - override val thisTransition: FlickerBuilder.() -> Unit = { - transitions { tapl.goHome() } - } +open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : + EnterPipViaAppUiButtonTest(flicker) { + override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } override val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { @@ -73,7 +73,7 @@ open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiBu } } - @Presubmit + @FlakyTest(bugId = 293133362) @Test override fun pipLayerReduces() { flicker.assertLayers { @@ -85,7 +85,7 @@ open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiBu } /** Checks that [pipApp] window is animated towards default position in right bottom corner */ - @Presubmit + @FlakyTest(bugId = 255578530) @Test fun pipLayerMovesTowardsRightBottomCorner() { // in gestural nav the swipe makes PiP first go upwards @@ -108,4 +108,10 @@ open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiBu Assume.assumeFalse(flicker.scenario.isGesturalNavigation) super.focusChanges() } + + @FlakyTest(bugId = 289943985) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt index afcc1729ed16..9256725c6180 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.flicker.pip.common.ClosePipTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -49,11 +49,10 @@ import org.junit.runners.Parameterized * apps are running before setup * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ClosePipBySwipingDownTest(flicker: FlickerTest) : ClosePipTransition(flicker) { +class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { val pipRegion = wmHelper.getWindowRegion(pipApp).bounds diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt deleted file mode 100644 index 02f60100d069..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.common.Rotation -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -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 ClosePipBySwipingDownTestCfArm(flicker: FlickerTest) : ClosePipBySwipingDownTest(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(Rotation.ROTATION_0) - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt index 86fe583c94e6..002c019eff93 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt @@ -19,8 +19,8 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.flicker.pip.common.ClosePipTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -49,11 +49,10 @@ import org.junit.runners.Parameterized * apps are running before setup * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ClosePipWithDismissButtonTest(flicker: FlickerTest) : ClosePipTransition(flicker) { +class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.closePipWindow(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt deleted file mode 100644 index 05262feceba5..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.common.Rotation -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -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) -open class ClosePipWithDismissButtonTestCfArm(flicker: FlickerTest) : - ClosePipWithDismissButtonTest(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(Rotation.ROTATION_0) - ) - } - } -} 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 01d67cc35a14..6dd68b0d029d 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 @@ -19,8 +19,8 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.flicker.pip.common.EnterPipTransition import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -40,14 +40,11 @@ import org.junit.runners.Parameterized * Press Home button or swipe up to go Home and put [pipApp] in pip mode * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransition(flicker) { - override val thisTransition: FlickerBuilder.() -> Unit = { - transitions { tapl.goHome() } - } +class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { + override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } override val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { 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 deleted file mode 100644 index 90f99c0c4cae..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -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/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index 5480144ba1ce..8207b85c3e0c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -17,23 +17,24 @@ 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.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils -import androidx.test.filters.RequiresDevice +import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper 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.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.pip.common.PipTransition +import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE +import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -64,19 +65,16 @@ import org.junit.runners.Parameterized * apps are running before setup * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flicker) { +class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val testApp = FixedOrientationAppHelper(instrumentation) private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) override val thisTransition: FlickerBuilder.() -> Unit = { - teardown { - testApp.exit(wmHelper) - } + teardown { testApp.exit(wmHelper) } transitions { // Enter PiP, and assert that the PiP is within bounds now that the device is back // in portrait @@ -95,14 +93,13 @@ open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flic setup { // Launch a portrait only app on the fullscreen stack testApp.launchViaIntent( - wmHelper, - stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()) + wmHelper, + stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()) ) // Launch the PiP activity fixed as landscape, but don't enter PiP pipApp.launchViaIntent( - wmHelper, - stringExtras = - mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) + wmHelper, + stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) ) } } @@ -207,13 +204,13 @@ open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flic /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + return LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt deleted file mode 100644 index 58416660826f..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.common.Rotation -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -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 EnterPipToOtherOrientationCfArm(flicker: FlickerTest) : - EnterPipToOtherOrientation(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(Rotation.ROTATION_0) - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt index 95725b64a48a..cc943678d492 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt @@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.flicker.pip.common.EnterPipTransition import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -46,11 +46,10 @@ import org.junit.runners.Parameterized * apps are running before setup * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterPipViaAppUiButtonTest(flicker: FlickerTest) : EnterPipTransition(flicker) { +open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.clickEnterPipButton(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt deleted file mode 100644 index 4390f0bb70b2..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.common.Rotation -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -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 EnterPipViaAppUiButtonTestCfArm(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(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(Rotation.ROTATION_0) - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt index 0b3d16a8087d..7da442901e40 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt @@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -48,11 +48,11 @@ import org.junit.runners.Parameterized * apps are running before setup * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) { +class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) : + ExitPipToAppTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { setup { // launch an app behind the pip one diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt deleted file mode 100644 index eccb85d98798..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.common.Rotation -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -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 ExitPipToAppViaExpandButtonTestCfArm(flicker: FlickerTest) : - ExitPipToAppViaExpandButtonTest(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(Rotation.ROTATION_0) - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt index bb2d40becdc9..0ad9e4c61d83 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt @@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -47,11 +47,10 @@ import org.junit.runners.Parameterized * apps are running before setup * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) { +class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { setup { // launch an app behind the pip one diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt deleted file mode 100644 index 6ab6a1f0bb73..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.common.Rotation -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -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 ExitPipToAppViaIntentTestCfArm(flicker: FlickerTest) : ExitPipToAppViaIntentTest(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(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 fd16b6ea6ada..89a6c93e478c 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 @@ -21,9 +21,9 @@ import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -51,11 +51,10 @@ import org.junit.runners.Parameterized * apps are running before setup * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) { +class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.doubleClickPipWindow(wmHelper) } } @@ -142,15 +141,14 @@ open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flic /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt deleted file mode 100644 index c09623490041..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.common.Rotation -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -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 ExpandPipOnDoubleClickTestTestCfArm(flicker: FlickerTest) : - ExpandPipOnDoubleClickTest(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(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 index 253aa4cae5c7..8978af0088b8 100644 --- 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 @@ -20,9 +20,9 @@ import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -import androidx.test.filters.RequiresDevice +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -30,11 +30,10 @@ 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) -open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) { +class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) } } @@ -55,15 +54,14 @@ open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicke /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt deleted file mode 100644 index e064bf2ee921..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.common.Rotation -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -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 ExpandPipOnPinchOpenTestCfArm(flicker: FlickerTest) : ExpandPipOnPinchOpenTest(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(Rotation.ROTATION_0) - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt index 094060f86691..4776206724cc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt @@ -19,9 +19,10 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.Direction +import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition +import com.android.wm.shell.flicker.utils.Direction import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -55,7 +56,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class MovePipDownOnShelfHeightChange(flicker: FlickerTest) : MovePipShelfHeightTransition(flicker) { +class MovePipDownOnShelfHeightChange(flicker: LegacyFlickerTest) : + MovePipShelfHeightTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { teardown { testApp.exit(wmHelper) } transitions { testApp.launchViaIntent(wmHelper) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt index ff51c27bf116..425cbfaffedd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt @@ -18,15 +18,16 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils -import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.setRotation +import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -34,11 +35,10 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** Test Pip launch. To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) { +class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val imeApp = ImeAppHelper(instrumentation) override val thisTransition: FlickerBuilder.() -> Unit = { @@ -80,7 +80,7 @@ open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransitio @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + return LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt deleted file mode 100644 index d3d77d20662e..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.common.Rotation -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -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 MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) : - MovePipOnImeVisibilityChangeTest(flicker) { - companion object { - private const val TAG_IME_VISIBLE = "imeIsVisible" - - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(Rotation.ROTATION_0) - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt index 27b061b67a85..18f30d96938b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt @@ -19,9 +19,10 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.Direction +import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition +import com.android.wm.shell.flicker.utils.Direction import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -55,14 +56,13 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class MovePipUpOnShelfHeightChangeTest(flicker: FlickerTest) : +open class MovePipUpOnShelfHeightChangeTest(flicker: LegacyFlickerTest) : MovePipShelfHeightTransition(flicker) { - override val thisTransition: FlickerBuilder.() -> Unit = - { - setup { testApp.launchViaIntent(wmHelper) } - transitions { tapl.pressHome() } - teardown { testApp.exit(wmHelper) } - } + override val thisTransition: FlickerBuilder.() -> Unit = { + setup { testApp.launchViaIntent(wmHelper) } + transitions { tapl.pressHome() } + teardown { testApp.exit(wmHelper) } + } /** Checks that the visible region of [pipApp] window always moves up during the animation. */ @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt index 9f81ba8eee87..c7f2786debd0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt @@ -16,13 +16,14 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -34,7 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) { +class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { private var isDraggedLeft: Boolean = true override val thisTransition: FlickerBuilder.() -> Unit = { @@ -62,7 +63,7 @@ class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) { } } - @Postsubmit + @Presubmit @Test fun pipLayerMovesAwayFromEdge() { flicker.assertLayers { @@ -81,13 +82,11 @@ class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt index 9fe9f52fd4af..cabc1cc0b023 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt @@ -17,16 +17,17 @@ package com.android.wm.shell.flicker.pip import android.graphics.Rect -import android.platform.test.annotations.Postsubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -34,11 +35,12 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** Test the snapping of a PIP window via dragging, releasing, and checking its final location. */ +@FlakyTest(bugId = 294993100) @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) { +class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { // represents the direction in which the pip window should be snapping private var willSnapRight: Boolean = true @@ -80,8 +82,8 @@ class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) { /** * Checks that the visible region area of [pipApp] moves to closest edge during the animation. */ - @Postsubmit @Test + @FlakyTest(bugId = 294993100) fun pipLayerMovesToClosestEdge() { flicker.assertLayers { val pipLayerList = layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } @@ -95,19 +97,102 @@ class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) { } } + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun entireScreenCovered() { + super.entireScreenCovered() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun hasAtMostOnePipDismissOverlayWindow() { + super.hasAtMostOnePipDismissOverlayWindow() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun navBarLayerIsVisibleAtStartAndEnd() { + super.navBarLayerIsVisibleAtStartAndEnd() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun navBarLayerPositionAtStartAndEnd() { + super.navBarLayerPositionAtStartAndEnd() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun navBarWindowIsAlwaysVisible() { + super.navBarWindowIsAlwaysVisible() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun statusBarLayerIsVisibleAtStartAndEnd() { + super.statusBarLayerIsVisibleAtStartAndEnd() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun statusBarLayerPositionAtStartAndEnd() { + super.statusBarLayerPositionAtStartAndEnd() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun statusBarWindowIsAlwaysVisible() { + super.statusBarWindowIsAlwaysVisible() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun taskBarLayerIsVisibleAtStartAndEnd() { + super.taskBarLayerIsVisibleAtStartAndEnd() + } + + // Overridden to remove @Presubmit annotation + @Test + @FlakyTest(bugId = 294993100) + override fun taskBarWindowIsAlwaysVisible() { + super.taskBarWindowIsAlwaysVisible() + } + companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index 60bf5ffdc7af..6748626d4e46 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -16,14 +16,14 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -35,14 +35,13 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@FlakyTest(bugId = 270677470) -class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) { +class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } } /** Checks that the visible region area of [pipApp] always decreases during the animation. */ - @Postsubmit + @Presubmit @Test fun pipLayerAreaDecreases() { flicker.assertLayers { @@ -57,15 +56,14 @@ class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt index c618e5a24fdf..1f69847e5481 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt @@ -17,19 +17,21 @@ 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.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION -import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE +import com.android.wm.shell.flicker.pip.common.PipTransition +import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -46,7 +48,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransition(flicker) { +open class SetRequestedOrientationWhilePinned(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) @@ -69,20 +71,19 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit setup { // Launch the PiP activity fixed as landscape. pipApp.launchViaIntent( - wmHelper, - stringExtras = - mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) + 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(Rotation.ROTATION_0) - .withNavOrTaskBarVisible() - .withStatusBarVisible() - .waitForAndVerify() + .StateSyncBuilder() + .withPipShown() + .withRotation(Rotation.ROTATION_0) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() } } @@ -150,7 +151,7 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + return LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt index 43d6c8f26126..308ece40402f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt @@ -17,14 +17,15 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.common.flicker.assertions.FlickerTest import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils -import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.setRotation +import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -54,11 +55,10 @@ import org.junit.runners.Parameterized * apps are running before setup * ``` */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker) { +class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val testApp = SimpleAppHelper(instrumentation) private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation) @@ -154,13 +154,13 @@ open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.rotationTests() + return LegacyFlickerTestFactory.rotationTests() } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt deleted file mode 100644 index b7a2c47e3b32..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -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 ShowPipAndRotateDisplayCfArm(flicker: FlickerTest) : ShowPipAndRotateDisplay(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(): Collection<FlickerTest> { - return FlickerTestFactory.rotationTests() - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt new file mode 100644 index 000000000000..c9a98c73e5e5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt @@ -0,0 +1,236 @@ +/* + * 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.apps + +import android.platform.test.annotations.Postsubmit +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import com.android.wm.shell.flicker.pip.common.EnterPipTransition +import org.junit.Test +import org.junit.runners.Parameterized + +abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { + protected abstract val standardAppHelper: StandardAppHelper + + /** Checks [standardAppHelper] window remains visible throughout the animation */ + @Postsubmit + @Test + override fun pipAppWindowAlwaysVisible() { + flicker.assertWm { this.isAppWindowVisible(standardAppHelper.packageNameMatcher) } + } + + /** Checks [standardAppHelper] layer remains visible throughout the animation */ + @Postsubmit + @Test + override fun pipAppLayerAlwaysVisible() { + flicker.assertLayers { this.isVisible(standardAppHelper.packageNameMatcher) } + } + + /** Checks the content overlay appears then disappears during the animation */ + @Postsubmit + @Test + override fun pipOverlayLayerAppearThenDisappear() { + super.pipOverlayLayerAppearThenDisappear() + } + + /** + * Checks that [standardAppHelper] window remains inside the display bounds throughout the whole + * animation + */ + @Postsubmit + @Test + override fun pipWindowRemainInsideVisibleBounds() { + flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) { + coversAtMost(displayBounds) + } + } + + /** + * Checks that the [standardAppHelper] layer remains inside the display bounds throughout the + * whole animation + */ + @Postsubmit + @Test + override fun pipLayerOrOverlayRemainInsideVisibleBounds() { + flicker.assertLayersVisibleRegion( + standardAppHelper.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY) + ) { + coversAtMost(displayBounds) + } + } + + /** Checks that the visible region of [standardAppHelper] always reduces during the animation */ + @Postsubmit + @Test + override fun pipLayerReduces() { + flicker.assertLayers { + val pipLayerList = this.layers { + standardAppHelper.layerMatchesAnyOf(it) && it.isVisible + } + pipLayerList.zipWithNext { previous, current -> + current.visibleRegion.notBiggerThan(previous.visibleRegion.region) + } + } + } + + /** Checks that [standardAppHelper] window becomes pinned */ + @Postsubmit + @Test + override fun pipWindowBecomesPinned() { + flicker.assertWm { + invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper.packageNameMatcher) } + .then() + .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper.packageNameMatcher) } + } + } + + /** Checks [ComponentNameMatcher.LAUNCHER] layer remains visible throughout the animation */ + @Postsubmit + @Test + override fun launcherLayerBecomesVisible() { + super.launcherLayerBecomesVisible() + } + + /** + * Checks that the focus changes between the [standardAppHelper] window and the launcher when + * closing the pip window + */ + @Postsubmit + @Test + override fun focusChanges() { + flicker.assertEventLog { + this.focusChanges(standardAppHelper.packageName, "NexusLauncherActivity") + } + } + + @Postsubmit + @Test + override fun hasAtMostOnePipDismissOverlayWindow() = super.hasAtMostOnePipDismissOverlayWindow() + + // ICommonAssertions.kt overrides due to Morris overlay + + /** + * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition + */ + @Postsubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() { + // this fails due to Morris overlay + } + + /** + * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the + * transition + */ + @Postsubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() { + // this fails due to Morris overlay + } + + /** + * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition + * + * Note: Phones only + */ + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() { + // this fails due to Morris overlay + } + + /** + * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition + */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** + * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition + * + * Note: Large screen only + */ + @Postsubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + /** + * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole + * transition + */ + @Postsubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** + * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the + * transition + */ + @Postsubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + /** + * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole + * transition + */ + @Postsubmit + @Test override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + /** + * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive + * entries. + */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** + * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive + * entries. + */ + @Postsubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + /** Checks that all parts of the screen are covered during the transition */ + @Postsubmit + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + companion object { + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt new file mode 100644 index 000000000000..d7ba3d57b548 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt @@ -0,0 +1,143 @@ +/* + * 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.apps + +import android.content.Context +import android.location.Criteria +import android.location.Location +import android.location.LocationManager +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import android.platform.test.annotations.Postsubmit +import android.tools.device.apphelpers.MapsAppHelper +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import androidx.test.filters.RequiresDevice +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 + +/** + * Test entering pip from Maps app by interacting with the app UI + * + * To run this test: `atest WMShellFlickerTests:MapsEnterPipTest` + * + * Actions: + * ``` + * Launch Maps and start navigation mode + * Go home to enter PiP + * ``` + * + * 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 + * [android.tools.device.flicker.legacy.runner.TransitionRunner], + * 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) +open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) { + override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation) + + val locationManager: LocationManager = + instrumentation.context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + val mainHandler = Handler(Looper.getMainLooper()) + var mockLocationEnabled = false + + val updateLocation = object : Runnable { + override fun run() { + // early bail out if mocking location is not enabled + if (!mockLocationEnabled) return + val location = Location("Googleplex") + location.latitude = 37.42243438411294 + location.longitude = -122.08426281892311 + location.time = System.currentTimeMillis() + location.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos() + location.accuracy = 100f + locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, location) + mainHandler.postDelayed(this, 5) + } + } + + override val defaultEnterPip: FlickerBuilder.() -> Unit = { + setup { + locationManager.addTestProvider( + LocationManager.GPS_PROVIDER, + false, + false, + false, + false, + false, + false, + false, + Criteria.POWER_LOW, + Criteria.ACCURACY_FINE + ) + locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true) + mockLocationEnabled = true + mainHandler.post(updateLocation) + + // normal app open through the Launcher All Apps + // var mapsAddressOption = "Golden Gate Bridge" + // standardAppHelper.open() + // standardAppHelper.doSearch(mapsAddressOption) + // standardAppHelper.getDirections() + // standardAppHelper.startNavigation(); + + standardAppHelper.launchViaIntent( + wmHelper, + MapsAppHelper.getMapIntent(MapsAppHelper.INTENT_NAVIGATION) + ) + + standardAppHelper.waitForNavigationToStart() + } + } + + override val defaultTeardown: FlickerBuilder.() -> Unit = { + teardown { + standardAppHelper.exit(wmHelper) + mainHandler.removeCallbacks(updateLocation) + // the main looper callback might have tried to provide a new location after the + // provider is no longer in test mode, causing a crash, this prevents it from happening + mockLocationEnabled = false + locationManager.removeTestProvider(LocationManager.GPS_PROVIDER) + } + } + + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { tapl.goHome() } + } + + @Postsubmit + @Test + override fun focusChanges() { + // in gestural nav the focus goes to different activity on swipe up with auto enter PiP + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) + super.focusChanges() + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt new file mode 100644 index 000000000000..c370d91034fd --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt @@ -0,0 +1,95 @@ +/* + * 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.apps + +import android.platform.test.annotations.Postsubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.YouTubeAppHelper +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import androidx.test.filters.RequiresDevice +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 + +/** + * Test entering pip from YouTube app by interacting with the app UI + * + * To run this test: `atest WMShellFlickerTests:YouTubeEnterPipTest` + * + * Actions: + * ``` + * Launch YouTube and start playing a video + * Go home to enter PiP + * ``` + * + * 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 + * [android.tools.device.flicker.legacy.runner.TransitionRunner], + * 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) +open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) { + override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation) + + override val defaultEnterPip: FlickerBuilder.() -> Unit = { + setup { + standardAppHelper.launchViaIntent( + wmHelper, + YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"), + ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "") + ) + standardAppHelper.waitForVideoPlaying() + } + } + + override val defaultTeardown: FlickerBuilder.() -> Unit = { + teardown { + standardAppHelper.exit(wmHelper) + } + } + + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { tapl.goHome() } + } + + @Postsubmit + @Test + override fun pipOverlayLayerAppearThenDisappear() { + // YouTube uses source rect hint, so PiP overlay is never present + } + + @Postsubmit + @Test + override fun focusChanges() { + // in gestural nav the focus goes to different activity on swipe up with auto enter PiP + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) + super.focusChanges() + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt index e52b71e602f9..751f2bc0807f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,20 +14,20 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.pip +package com.android.wm.shell.flicker.pip.common import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher.Companion.LAUNCHER import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import com.android.server.wm.flicker.helpers.setRotation import org.junit.Test import org.junit.runners.Parameterized /** Base class for exiting pip (closing pip window) without returning to the app */ -abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) { +abstract class ClosePipTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { setup { this.setRotation(flicker.scenario.startRotation) } teardown { this.setRotation(Rotation.ROTATION_0) } @@ -74,15 +74,14 @@ abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt index cdbdb85a9195..9c129e47efba 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt @@ -14,22 +14,20 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.pip +package com.android.wm.shell.flicker.pip.common import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.Test import org.junit.runners.Parameterized -abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) { +abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val defaultEnterPip: FlickerBuilder.() -> Unit = { - setup { - pipApp.launchViaIntent(wmHelper) - } + setup { pipApp.launchViaIntent(wmHelper) } } /** Checks [pipApp] window remains visible throughout the animation */ @@ -93,7 +91,7 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) /** Checks that [pipApp] window becomes pinned */ @Presubmit @Test - fun pipWindowBecomesPinned() { + open fun pipWindowBecomesPinned() { flicker.assertWm { invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp) } .then() @@ -104,7 +102,7 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) /** Checks [ComponentNameMatcher.LAUNCHER] layer remains visible throughout the animation */ @Presubmit @Test - fun launcherLayerBecomesVisible() { + open fun launcherLayerBecomesVisible() { flicker.assertLayers { isInvisible(ComponentNameMatcher.LAUNCHER) .then() @@ -119,22 +117,21 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) @Presubmit @Test open fun focusChanges() { - flicker.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") } + flicker.assertEventLog { this.focusChanges(pipApp.packageName, "NexusLauncherActivity") } } companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(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/common/ExitPipToAppTransition.kt index 5ac9829b6c8f..9450bdd2d894 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/common/ExitPipToAppTransition.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,19 +14,19 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.pip +package com.android.wm.shell.flicker.pip.common import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import com.android.server.wm.flicker.helpers.SimpleAppHelper import org.junit.Test import org.junit.runners.Parameterized /** Base class for pip expand tests */ -abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flicker) { +abstract class ExitPipToAppTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) { protected val testApp = SimpleAppHelper(instrumentation) /** @@ -130,15 +130,14 @@ abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flic /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(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/common/MovePipShelfHeightTransition.kt index 109354ab5c79..7e42bc11a9c1 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/common/MovePipShelfHeightTransition.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,20 +14,20 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.pip +package com.android.wm.shell.flicker.pip.common import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.flicker.subject.region.RegionSubject -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper -import com.android.wm.shell.flicker.Direction +import com.android.wm.shell.flicker.utils.Direction import org.junit.Test import org.junit.runners.Parameterized /** Base class for pip tests with Launcher shelf height change */ -abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransition(flicker) { +abstract class MovePipShelfHeightTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) { protected val testApp = FixedOrientationAppHelper(instrumentation) /** Checks [pipApp] window remains visible throughout the animation */ @@ -111,15 +111,14 @@ abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransitio /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } 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/common/PipTransition.kt index 17a178f78de3..7b7f1d7b5a82 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/common/PipTransition.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.pip +package com.android.wm.shell.flicker.pip.common import android.app.Instrumentation import android.content.Intent @@ -22,7 +22,7 @@ import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import android.tools.device.helpers.WindowUtils import com.android.server.wm.flicker.helpers.PipAppHelper @@ -32,7 +32,7 @@ import com.android.wm.shell.flicker.BaseTest import com.google.common.truth.Truth import org.junit.Test -abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) { +abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) { protected val pipApp = PipAppHelper(instrumentation) protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) @@ -78,21 +78,21 @@ abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) { /** Defines the default method of entering PiP */ protected open val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { - pipApp.launchViaIntentAndWaitForPip(wmHelper, - stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")) + pipApp.launchViaIntentAndWaitForPip( + wmHelper, + stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true") + ) } } /** Defines the default teardown required to clean up after the test */ protected open val defaultTeardown: FlickerBuilder.() -> Unit = { - teardown { - pipApp.exit(wmHelper) - } + teardown { pipApp.exit(wmHelper) } } @Presubmit @Test - fun hasAtMostOnePipDismissOverlayWindow() { + open fun hasAtMostOnePipDismissOverlayWindow() { val matcher = ComponentNameMatcher("", "pip-dismiss-overlay") flicker.assertWm { val overlaysPerState = 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 index 000ae8f9458e..c6cbcd052fe0 100644 --- 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 @@ -26,7 +26,7 @@ import com.android.server.wm.flicker.helpers.PipAppHelper /** Helper class for PIP app on AndroidTV */ open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) { - private val appSelector = By.pkg(`package`).depth(0) + private val appSelector = By.pkg(packageName).depth(0) val ui: UiObject2? get() = uiDevice.findObject(appSelector) @@ -46,7 +46,7 @@ open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instr } override fun clickObject(resId: String) { - val selector = By.res(`package`, resId) + val selector = By.res(packageName, resId) focusOnObject(selector) || error("Could not focus on `$resId` object") uiDevice.pressDPadCenter() } @@ -68,7 +68,7 @@ open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instr } fun waitUntilClosed(): Boolean { - val appSelector = By.pkg(`package`).depth(0) + val appSelector = By.pkg(packageName).depth(0) return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS) } 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 0432a8497fbe..d4cd6da4acb1 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 @@ -20,8 +20,8 @@ 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.wait +import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.utils.wait import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before 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 90406c510bad..4402e2153e9b 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 @@ -21,11 +21,11 @@ import android.app.PendingIntent import android.os.Bundle import android.service.notification.StatusBarNotification import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.NotificationListener.Companion.findNotification -import com.android.wm.shell.flicker.NotificationListener.Companion.startNotificationListener -import com.android.wm.shell.flicker.NotificationListener.Companion.stopNotificationListener -import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToAppear -import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToDisappear +import com.android.wm.shell.flicker.utils.NotificationListener.Companion.findNotification +import com.android.wm.shell.flicker.utils.NotificationListener.Companion.startNotificationListener +import com.android.wm.shell.flicker.utils.NotificationListener.Companion.stopNotificationListener +import com.android.wm.shell.flicker.utils.NotificationListener.Companion.waitForNotificationToAppear +import com.android.wm.shell.flicker.utils.NotificationListener.Companion.waitForNotificationToDisappear import org.junit.After import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull 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 6104b7bdacba..47bff8de377e 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 @@ -24,7 +24,7 @@ import android.tools.device.helpers.wakeUpAndGoToHomeScreen import android.tools.device.traces.parsers.WindowManagerStateHelper import android.view.Surface.ROTATION_0 import android.view.Surface.rotationToString -import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME import org.junit.After import org.junit.Assert.assertFalse import org.junit.Assume.assumeTrue 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 b0adbe1d07ce..4aee61ade10e 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 @@ -22,7 +22,7 @@ import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until -import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME /** Id of the root view in the com.android.wm.shell.pip.tv.PipMenuActivity */ private const val TV_PIP_MENU_ROOT_ID = "tv_pip_menu" diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt new file mode 100644 index 000000000000..5f157856aa36 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt @@ -0,0 +1,55 @@ +/* + * 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.service.common + +import android.app.Instrumentation +import android.platform.test.rule.NavigationModeRule +import android.platform.test.rule.PressHomeRule +import android.platform.test.rule.UnlockScreenRule +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.apphelpers.MessagingAppHelper +import android.tools.device.flicker.rules.ArtifactSaverRule +import android.tools.device.flicker.rules.ChangeDisplayOrientationRule +import android.tools.device.flicker.rules.LaunchAppRule +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.RuleChain + +object Utils { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + + fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain { + return RuleChain.outerRule(ArtifactSaverRule()) + .around(UnlockScreenRule()) + .around( + NavigationModeRule(navigationMode.value, false) + ) + .around( + LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false) + ) + .around(RemoveAllTasksButHomeRule()) + .around( + ChangeDisplayOrientationRule( + rotation, + resetOrientationAfterTest = false, + clearCacheAfterParsing = false + ) + ) + .around(PressHomeRule()) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS new file mode 100644 index 000000000000..3ab6a1ee061d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS @@ -0,0 +1,2 @@ +# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Split Screen +# Bug component: 928697 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt new file mode 100644 index 000000000000..a5c512267508 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt @@ -0,0 +1,40 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) { + @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt new file mode 100644 index 000000000000..092fb6720e57 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt @@ -0,0 +1,40 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) { + @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt new file mode 100644 index 000000000000..8cb25fe531b8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DismissSplitScreenByDividerGesturalNavLandscape : + DismissSplitScreenByDivider(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_EXIT"]) + @Test + override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt new file mode 100644 index 000000000000..2539fd50d742 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt @@ -0,0 +1,45 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DismissSplitScreenByDividerGesturalNavPortrait : + DismissSplitScreenByDivider(Rotation.ROTATION_0) { + + // TODO(b/300260196): Not detecting this scenario right now + @ExpectedScenarios(["ENTIRE_TRACE"]) + @Test + override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt new file mode 100644 index 000000000000..aa35237b615f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DismissSplitScreenByGoHomeGesturalNavLandscape : + DismissSplitScreenByGoHome(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_EXIT"]) + @Test + override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt new file mode 100644 index 000000000000..e195360cdc36 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DismissSplitScreenByGoHomeGesturalNavPortrait : + DismissSplitScreenByGoHome(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_EXIT"]) + @Test + override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt new file mode 100644 index 000000000000..c1b3aade11cd --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt @@ -0,0 +1,43 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"]) + @Test + override fun dragDividerToResize() = super.dragDividerToResize() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt new file mode 100644 index 000000000000..c6e2e854ab89 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt @@ -0,0 +1,43 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"]) + @Test + override fun dragDividerToResize() = super.dragDividerToResize() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt new file mode 100644 index 000000000000..5f771c7545c7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape : + EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt new file mode 100644 index 000000000000..729a401fe34c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait : + EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt new file mode 100644 index 000000000000..6e4cf9f55cfd --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt @@ -0,0 +1,45 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromNotificationGesturalNavLandscape : + EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromNotification() = + super.enterSplitScreenByDragFromNotification() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt new file mode 100644 index 000000000000..cc2870213e8d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt @@ -0,0 +1,45 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromNotificationGesturalNavPortrait : + EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromNotification() = + super.enterSplitScreenByDragFromNotification() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt new file mode 100644 index 000000000000..736604f02377 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromShortcutGesturalNavLandscape : + EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt new file mode 100644 index 000000000000..8df8dfaab071 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromShortcutGesturalNavPortrait : + EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt new file mode 100644 index 000000000000..378f055ef830 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape : + EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt new file mode 100644 index 000000000000..b33d26288d33 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait : + EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt new file mode 100644 index 000000000000..b1d3858b9dbb --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenFromOverviewGesturalNavLandscape : + EnterSplitScreenFromOverview(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt new file mode 100644 index 000000000000..6d824c74c1fe --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenFromOverviewGesturalNavPortrait : + EnterSplitScreenFromOverview(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt new file mode 100644 index 000000000000..f1d3d0cf2716 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchAppByDoubleTapDividerGesturalNavLandscape : + SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) { + + @ExpectedScenarios([]) + @Test + override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt new file mode 100644 index 000000000000..a867bac8c0eb --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchAppByDoubleTapDividerGesturalNavPortrait : + SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) { + + @ExpectedScenarios([]) + @Test + override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt new file mode 100644 index 000000000000..76247ba10037 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromAnotherAppGesturalNavLandscape : + SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt new file mode 100644 index 000000000000..e179da81e4db --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromAnotherAppGesturalNavPortrait : + SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt new file mode 100644 index 000000000000..20f554f7d154 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromHomeGesturalNavLandscape : + SwitchBackToSplitFromHome(Rotation.ROTATION_90) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt new file mode 100644 index 000000000000..f7776ee3244a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromHomeGesturalNavPortrait : + SwitchBackToSplitFromHome(Rotation.ROTATION_0) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt new file mode 100644 index 000000000000..4ff0b4362e60 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromRecentGesturalNavLandscape : + SwitchBackToSplitFromRecent(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt new file mode 100644 index 000000000000..930f31d1f348 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromRecentGesturalNavPortrait : + SwitchBackToSplitFromRecent(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt new file mode 100644 index 000000000000..3da61e5e310c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt @@ -0,0 +1,43 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt new file mode 100644 index 000000000000..627ae1843314 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt @@ -0,0 +1,43 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt new file mode 100644 index 000000000000..c744103d49ba --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt @@ -0,0 +1,42 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() { + + @ExpectedScenarios(["LOCKSCREEN_UNLOCK_ANIMATION"]) + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt new file mode 100644 index 000000000000..11a4e02c5e37 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt @@ -0,0 +1,42 @@ +/* + * 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.service.splitscreen.flicker + +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() { + + @ExpectedScenarios(["LOCKSCREEN_UNLOCK_ANIMATION"]) + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt new file mode 100644 index 000000000000..e37d806c7a14 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test + +open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun copyContentInSplit() = super.copyContentInSplit() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt new file mode 100644 index 000000000000..2a50912e0a5c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test + +open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun copyContentInSplit() = super.copyContentInSplit() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt new file mode 100644 index 000000000000..d5da1a8b558c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test + +open class DismissSplitScreenByDividerGesturalNavLandscape : + DismissSplitScreenByDivider(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt new file mode 100644 index 000000000000..7fdcb9be62ee --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test + +open class DismissSplitScreenByDividerGesturalNavPortrait : + DismissSplitScreenByDivider(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt new file mode 100644 index 000000000000..308e954b86c1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test + +open class DismissSplitScreenByGoHomeGesturalNavLandscape : + DismissSplitScreenByGoHome(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt new file mode 100644 index 000000000000..39e75bd25a71 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test + +open class DismissSplitScreenByGoHomeGesturalNavPortrait : + DismissSplitScreenByGoHome(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt new file mode 100644 index 000000000000..e18da17175c0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test + +open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dragDividerToResize() = super.dragDividerToResize() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt new file mode 100644 index 000000000000..00d60e756ffa --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test + +open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dragDividerToResize() = super.dragDividerToResize() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt new file mode 100644 index 000000000000..d7efbc8c0fd4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test + +open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape : + EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt new file mode 100644 index 000000000000..4eece3f62d10 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test + +open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait : + EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt new file mode 100644 index 000000000000..d96b056d8753 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt @@ -0,0 +1,32 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test + +open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape : + EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromNotification() = + super.enterSplitScreenByDragFromNotification() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt new file mode 100644 index 000000000000..809b690e0861 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt @@ -0,0 +1,32 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test + +open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait : + EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromNotification() = + super.enterSplitScreenByDragFromNotification() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt new file mode 100644 index 000000000000..bbdf2d728494 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test + +open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape : + EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt new file mode 100644 index 000000000000..5c29fd8fe57e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test + +open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait : + EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt new file mode 100644 index 000000000000..a7398ebf56e8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test + +open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape : + EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt new file mode 100644 index 000000000000..eae88ad4ad09 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test + +open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait : + EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt new file mode 100644 index 000000000000..7e8ee04a28fa --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test + +open class EnterSplitScreenFromOverviewGesturalNavLandscape : + EnterSplitScreenFromOverview(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt new file mode 100644 index 000000000000..9295c330b879 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test + +open class EnterSplitScreenFromOverviewGesturalNavPortrait : + EnterSplitScreenFromOverview(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt new file mode 100644 index 000000000000..4b59e9fbd866 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test + +open class SwitchAppByDoubleTapDividerGesturalNavLandscape : + SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt new file mode 100644 index 000000000000..5ff36d4aabbb --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test + +open class SwitchAppByDoubleTapDividerGesturalNavPortrait : + SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt new file mode 100644 index 000000000000..c0cb7219437b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test + +open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape : + SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt new file mode 100644 index 000000000000..8c140884aa50 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test + +open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait : + SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt new file mode 100644 index 000000000000..7b6614b81c11 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test + +open class SwitchBackToSplitFromHomeGesturalNavLandscape : + SwitchBackToSplitFromHome(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt new file mode 100644 index 000000000000..5df5be9daa8b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test + +open class SwitchBackToSplitFromHomeGesturalNavPortrait : + SwitchBackToSplitFromHome(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt new file mode 100644 index 000000000000..9d63003bf2a1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test + +open class SwitchBackToSplitFromRecentGesturalNavLandscape : + SwitchBackToSplitFromRecent(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt new file mode 100644 index 000000000000..9fa04b208ad1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test + +open class SwitchBackToSplitFromRecentGesturalNavPortrait : + SwitchBackToSplitFromRecent(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt new file mode 100644 index 000000000000..9386aa2b2cf0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test + +open class SwitchBetweenSplitPairsGesturalNavLandscape : + SwitchBetweenSplitPairs(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt new file mode 100644 index 000000000000..5ef21672bfe0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test + +open class SwitchBetweenSplitPairsGesturalNavPortrait : + SwitchBetweenSplitPairs(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt new file mode 100644 index 000000000000..9caab9b5182a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt @@ -0,0 +1,32 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RunWith(BlockJUnit4ClassRunner::class) +open class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt new file mode 100644 index 000000000000..bf484e5cef98 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt @@ -0,0 +1,32 @@ +/* + * 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.service.splitscreen.platinum + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RunWith(BlockJUnit4ClassRunner::class) +open class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt new file mode 100644 index 000000000000..245184cf0327 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt @@ -0,0 +1,67 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class CopyContentInSplit +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val textEditApp = SplitScreenUtils.getIme(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) + } + + @Test + open fun copyContentInSplit() { + SplitScreenUtils.copyContentInSplit(instrumentation, device, primaryApp, textEditApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt new file mode 100644 index 000000000000..1f2f1ecc0aab --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt @@ -0,0 +1,80 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DismissSplitScreenByDivider +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dismissSplitScreenByDivider() { + 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() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt new file mode 100644 index 000000000000..ebbf7c5026c7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt @@ -0,0 +1,66 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DismissSplitScreenByGoHome +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dismissSplitScreenByGoHome() { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt new file mode 100644 index 000000000000..71e701c9e2be --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt @@ -0,0 +1,65 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DragDividerToResize +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dragDividerToResize() { + SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt new file mode 100644 index 000000000000..c433b211b53d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt @@ -0,0 +1,73 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromAllApps +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(tapl.isTablet) + + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromAllApps() { + tapl.launchedAppState.taskbar + .openAllApps() + .getAppIcon(secondaryApp.appName) + .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt new file mode 100644 index 000000000000..3f087a5b7ecd --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt @@ -0,0 +1,75 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromNotification +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(tapl.isTablet) + + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + // Send a notification + sendNotificationApp.launchViaIntent(wmHelper) + sendNotificationApp.postNotification(wmHelper) + tapl.goHome() + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromNotification() { + SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + sendNotificationApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt new file mode 100644 index 000000000000..767e7b555618 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt @@ -0,0 +1,83 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromShortcut +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(tapl.isTablet) + + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromShortcut() { + tapl.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .openDeepShortcutMenu() + .getMenuItem("Split Screen Secondary Activity") + .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + + // TODO: Do we want this check in here? Add to the other tests? + // flicker.splitScreenEntered( + // primaryApp, + // secondaryApp, + // fromOtherApp = false, + // appExistAtStart = false + // ) + } + + @After + fun teardwon() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt new file mode 100644 index 000000000000..2592fd40d902 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt @@ -0,0 +1,73 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromTaskbar +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(tapl.isTablet) + + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromTaskbar() { + tapl.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt new file mode 100644 index 000000000000..f2cbf24bd26e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt @@ -0,0 +1,73 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenFromOverview +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withHomeActivityVisible() + .waitForAndVerify() + } + + @Test + open fun enterSplitScreenFromOverview() { + SplitScreenUtils.splitFromOverview(tapl, device) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt new file mode 100644 index 000000000000..538ed960dac6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt @@ -0,0 +1,151 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.graphics.Point +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.helpers.WindowUtils +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchAppByDoubleTapDivider +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + tapl.workspace.switchToOverview().dismissAllTasks() + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun switchAppByDoubleTapDivider() { + SplitScreenUtils.doubleTapDividerToSwitch(device) + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + + waitForLayersToSwitch(wmHelper) + waitForWindowsToSwitch(wmHelper) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(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(rotation)) { + return@add if (isTablet()) { + secondaryAppWindow.frame.right <= primaryAppWindow.frame.left + } else { + primaryAppWindow.frame.right <= secondaryAppWindow.frame.left + } + } else { + return@add if (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(rotation)) { + return@add if (isTablet()) { + secondaryVisibleRegion.right <= primaryVisibleRegion.left + } else { + primaryVisibleRegion.right <= secondaryVisibleRegion.left + } + } else { + return@add if (isTablet()) { + primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } else { + primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } + } + } + .waitForAndVerify() + } + + private fun isLandscape(rotation: Rotation): Boolean { + val displayBounds = WindowUtils.getDisplayBounds(rotation) + return displayBounds.width > displayBounds.height + } + + private fun isTablet(): Boolean { + val sizeDp: Point = device.displaySizeDp + val LARGE_SCREEN_DP_THRESHOLD = 600 + return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt new file mode 100644 index 000000000000..0dab5ad622ad --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt @@ -0,0 +1,70 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromAnotherApp +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + thirdApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromAnotherApp() { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt new file mode 100644 index 000000000000..ad3a2d41f02a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt @@ -0,0 +1,69 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromHome +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromHome() { + tapl.workspace.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt new file mode 100644 index 000000000000..b780a1681762 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt @@ -0,0 +1,70 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromRecent +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + tapl.workspace.switchToOverview().dismissAllTasks() + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromRecent() { + tapl.workspace.switchToOverview().currentTask.open() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt new file mode 100644 index 000000000000..329d61dcdbf9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt @@ -0,0 +1,72 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBetweenSplitPairs +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val thirdApp = SplitScreenUtils.getIme(instrumentation) + private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp) + } + + @Test + open fun switchBetweenSplitPairs() { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + thirdApp.exit(wmHelper) + fourthApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt new file mode 100644 index 000000000000..a9933bbe09fc --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt @@ -0,0 +1,69 @@ +/* + * 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.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class UnlockKeyguardToSplitScreen { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(Rotation.ROTATION_0.value) + + SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp) + } + + @Test + open fun unlockKeyguardToSplitScreen() { + device.sleep() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + device.wakeUp() + device.pressMenu() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} 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 index d1f0980786bf..6b971699d212 100644 --- 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 @@ -16,26 +16,21 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.common.traces.component.EdgeExtensionComponentMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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 com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.utils.appWindowKeepVisible +import com.android.wm.shell.flicker.utils.layerKeepVisible +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsKeepVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -45,13 +40,13 @@ 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` + * To run this test: `atest WMShellFlickerTestsSplitScreen:CopyContentInSplit` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class CopyContentInSplit(override val flicker: FlickerTest) : +class CopyContentInSplit(override val flicker: LegacyFlickerTest) : CopyContentInSplitBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -60,21 +55,6 @@ class CopyContentInSplit(override val flicker: FlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override 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) @@ -136,8 +116,6 @@ class CopyContentInSplit(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.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 index 4505b9978b76..51588569a8aa 100644 --- 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 @@ -16,22 +16,22 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.helpers.WindowUtils +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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.splitScreenDividerBecomesInvisible import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible +import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.layerBecomesInvisible +import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesInvisible +import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesInvisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -41,13 +41,13 @@ import org.junit.runners.Parameterized /** * Test dismiss split screen by dragging the divider bar. * - * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByDivider` + * To run this test: `atest WMShellFlickerTestsSplitScreen:DismissSplitScreenByDivider` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class DismissSplitScreenByDivider(override val flicker: FlickerTest) : +class DismissSplitScreenByDivider(override val flicker: LegacyFlickerTest) : DismissSplitScreenByDividerBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit 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 index e05b22141e4d..fc6c2b3d7ce7 100644 --- 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 @@ -16,19 +16,19 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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.splitScreenDividerBecomesInvisible import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible +import com.android.wm.shell.flicker.utils.layerBecomesInvisible +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesInvisible +import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesInvisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -38,13 +38,13 @@ import org.junit.runners.Parameterized /** * Test dismiss split screen by go home. * - * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByGoHome` + * To run this test: `atest WMShellFlickerTestsSplitScreen:DismissSplitScreenByGoHome` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class DismissSplitScreenByGoHome(override val flicker: FlickerTest) : +class DismissSplitScreenByGoHome(override val flicker: LegacyFlickerTest) : DismissSplitScreenByGoHomeBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -154,8 +154,6 @@ class DismissSplitScreenByGoHome(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.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 index 63c5d14d85e1..8b1689a9d816 100644 --- 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 @@ -16,24 +16,19 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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 com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.utils.appWindowKeepVisible +import com.android.wm.shell.flicker.utils.layerKeepVisible +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsChanges import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -43,13 +38,13 @@ import org.junit.runners.Parameterized /** * Test resize split by dragging the divider bar. * - * To run this test: `atest WMShellFlickerTests:DragDividerToResize` + * To run this test: `atest WMShellFlickerTestsSplitScreen:DragDividerToResize` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class DragDividerToResize(override val flicker: FlickerTest) : +class DragDividerToResize(override val flicker: LegacyFlickerTest) : DragDividerToResizeBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -58,24 +53,11 @@ class DragDividerToResize(override val flicker: FlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override fun cujCompleted() { - flicker.appWindowIsVisibleAtStart(primaryApp) - flicker.appWindowIsVisibleAtStart(secondaryApp) - flicker.splitScreenDividerIsVisibleAtStart() - - flicker.appWindowIsVisibleAtEnd(primaryApp) - flicker.appWindowIsVisibleAtEnd(secondaryApp) - flicker.splitScreenDividerIsVisibleAtEnd() - } - @Presubmit @Test fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) - @Presubmit + @FlakyTest(bugId = 291678271) @Test fun primaryAppLayerVisibilityChanges() { flicker.assertLayers { @@ -87,7 +69,7 @@ class DragDividerToResize(override val flicker: FlickerTest) : } } - @Presubmit + @FlakyTest(bugId = 291678271) @Test fun secondaryAppLayerVisibilityChanges() { flicker.assertLayers { @@ -105,7 +87,7 @@ class DragDividerToResize(override val flicker: FlickerTest) : @Test fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp) - @FlakyTest(bugId = 245472831) + @FlakyTest(bugId = 291678271) @Test fun primaryAppBoundsChanges() { flicker.splitAppLayerBoundsChanges( @@ -115,7 +97,7 @@ class DragDividerToResize(override val flicker: FlickerTest) : ) } - @Presubmit + @FlakyTest(bugId = 291678271) @Test fun secondaryAppBoundsChanges() = flicker.splitAppLayerBoundsChanges( @@ -124,11 +106,15 @@ class DragDividerToResize(override val flicker: FlickerTest) : portraitPosTop = true ) + @FlakyTest(bugId = 291678271) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.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 e55868675da7..99613f39060d 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,25 +16,25 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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.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.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.utils.appWindowBecomesVisible +import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.layerBecomesVisible +import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -45,13 +45,13 @@ import org.junit.runners.Parameterized * 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` + * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromAllApps` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) : +class EnterSplitScreenByDragFromAllApps(override val flicker: LegacyFlickerTest) : EnterSplitScreenByDragFromAllAppsBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -160,11 +160,10 @@ class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(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 ab8ecc54e71c..756a7fa4ba98 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,24 +16,24 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT -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.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.layerBecomesVisible +import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -44,13 +44,13 @@ import org.junit.runners.Parameterized * 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` + * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromNotification` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest) : +class EnterSplitScreenByDragFromNotification(override val flicker: LegacyFlickerTest) : EnterSplitScreenByDragFromNotificationBenchmark(flicker), ICommonAssertions { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -162,11 +162,10 @@ class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest) companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(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 index 516ca97bc531..121b46acdb66 100644 --- 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 @@ -16,22 +16,22 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.layerBecomesVisible +import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -41,13 +41,13 @@ 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` + * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromShortcut` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) : +class EnterSplitScreenByDragFromShortcut(override val flicker: LegacyFlickerTest) : EnterSplitScreenByDragFromShortcutBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit @@ -105,11 +105,10 @@ class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(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 4af7e248b660..99deb9279271 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,25 +16,25 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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.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.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.utils.appWindowBecomesVisible +import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.layerBecomesVisible +import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -45,13 +45,13 @@ import org.junit.runners.Parameterized * 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` + * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromTaskbar` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) : +class EnterSplitScreenByDragFromTaskbar(override val flicker: LegacyFlickerTest) : EnterSplitScreenByDragFromTaskbarBenchmark(flicker), ICommonAssertions { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -163,10 +163,9 @@ class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedNavigationModes = listOf(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 index faad9e82ffef..212a4e3649dc 100644 --- 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 @@ -16,21 +16,21 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.appWindowBecomesVisible +import com.android.wm.shell.flicker.utils.layerBecomesVisible +import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisible +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -40,13 +40,13 @@ import org.junit.runners.Parameterized /** * Test enter split screen from Overview. * - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromOverview` + * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenFromOverview` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterSplitScreenFromOverview(override val flicker: FlickerTest) : +class EnterSplitScreenFromOverview(override val flicker: LegacyFlickerTest) : EnterSplitScreenFromOverviewBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -106,8 +106,6 @@ class EnterSplitScreenFromOverview(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OWNERS new file mode 100644 index 000000000000..3ab6a1ee061d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OWNERS @@ -0,0 +1,2 @@ +# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Split Screen +# Bug component: 928697 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 index 03b8a75a1f32..fac97c8cc8c4 100644 --- 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 @@ -16,25 +16,21 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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 com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.layerKeepVisible +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -44,13 +40,13 @@ import org.junit.runners.Parameterized /** * Test double tap the divider bar to switch the two apps. * - * To run this test: `atest WMShellFlickerTests:SwitchAppByDoubleTapDivider` + * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchAppByDoubleTapDivider` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) : +class SwitchAppByDoubleTapDivider(override val flicker: LegacyFlickerTest) : SwitchAppByDoubleTapDividerBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -59,19 +55,6 @@ class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override fun cujCompleted() { - flicker.appWindowIsVisibleAtStart(primaryApp) - flicker.appWindowIsVisibleAtStart(secondaryApp) - flicker.splitScreenDividerIsVisibleAtStart() - - flicker.appWindowIsVisibleAtEnd(primaryApp) - flicker.appWindowIsVisibleAtEnd(secondaryApp) - flicker.splitScreenDividerIsVisibleAtEnd() - } - @Presubmit @Test fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) @@ -120,11 +103,10 @@ class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(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 index 078d95de1dd0..284c32ea110d 100644 --- 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 @@ -16,20 +16,20 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.appWindowBecomesVisible +import com.android.wm.shell.flicker.utils.layerBecomesVisible +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -39,13 +39,13 @@ import org.junit.runners.Parameterized /** * Test quick switch to split pair from another app. * - * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromAnotherApp` + * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromAnotherApp` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) : +class SwitchBackToSplitFromAnotherApp(override val flicker: LegacyFlickerTest) : SwitchBackToSplitFromAnotherAppBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -149,11 +149,10 @@ class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(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 index 7c84243e00d7..9e6448f0bec9 100644 --- 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 @@ -16,20 +16,20 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.appWindowBecomesVisible +import com.android.wm.shell.flicker.utils.layerBecomesVisible +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -39,13 +39,13 @@ import org.junit.runners.Parameterized /** * Test quick switch to split pair from home. * - * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome` + * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromHome` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchBackToSplitFromHome(override val flicker: FlickerTest) : +class SwitchBackToSplitFromHome(override val flicker: LegacyFlickerTest) : SwitchBackToSplitFromHomeBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -149,11 +149,10 @@ class SwitchBackToSplitFromHome(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(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 index 7c46d3e099a2..8e28712cd993 100644 --- 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 @@ -16,20 +16,20 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.appWindowBecomesVisible +import com.android.wm.shell.flicker.utils.layerBecomesVisible +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -39,13 +39,13 @@ import org.junit.runners.Parameterized /** * Test switch back to split pair from recent. * - * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent` + * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromRecent` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) : +class SwitchBackToSplitFromRecent(override val flicker: LegacyFlickerTest) : SwitchBackToSplitFromRecentBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -149,11 +149,10 @@ class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(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 index 674ba40f6a1f..fb0193b1830d 100644 --- 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 @@ -16,28 +16,22 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -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 com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible +import com.android.wm.shell.flicker.utils.appWindowBecomesVisible +import com.android.wm.shell.flicker.utils.layerBecomesInvisible +import com.android.wm.shell.flicker.utils.layerBecomesVisible +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsSnapToDivider import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -47,13 +41,13 @@ import org.junit.runners.Parameterized /** * Test quick switch between two split pairs. * - * To run this test: `atest WMShellFlickerTests:SwitchBetweenSplitPairs` + * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBetweenSplitPairs` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchBetweenSplitPairs(override val flicker: FlickerTest) : +class SwitchBetweenSplitPairs(override val flicker: LegacyFlickerTest) : SwitchBetweenSplitPairsBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -62,21 +56,6 @@ class SwitchBetweenSplitPairs(override val flicker: FlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override 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() = @@ -223,8 +202,6 @@ class SwitchBetweenSplitPairs(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt new file mode 100644 index 000000000000..e59ed6491eca --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt @@ -0,0 +1,145 @@ +/* + * 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.splitscreen + +import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.helpers.PipAppHelper +import com.android.wm.shell.flicker.splitscreen.benchmark.SplitScreenBase +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import com.android.wm.shell.flicker.utils.layerBecomesInvisible +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsSnapToDivider +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 WMShellFlickerTestsSplitScreen:SwitchBetweenSplitPairsNoPip` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SwitchBetweenSplitPairsNoPip(override val flicker: LegacyFlickerTest) : + SplitScreenBase(flicker) { + + val thirdApp = SplitScreenUtils.getSendNotification(instrumentation) + val pipApp = PipAppHelper(instrumentation) + + override val transition: FlickerBuilder.() -> Unit + get() = { + defaultSetup(this) + defaultTeardown(this) + thisTransition(this) + } + + val thisTransition: FlickerBuilder.() -> Unit + get() = { + setup { + tapl.goHome() + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, pipApp) + pipApp.enableAutoEnterForPipActivity() + SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, pipApp) + } + transitions { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + teardown { + pipApp.exit(wmHelper) + thirdApp.exit(wmHelper) + } + } + + /** Checks that [pipApp] window won't enter pip */ + @Presubmit + @Test + fun notEnterPip() { + flicker.assertWm { isNotPinned(pipApp) } + } + + /** Checks the [pipApp] task did not reshow during transition. */ + @Presubmit + @Test + fun app1WindowIsVisibleOnceApp2WindowIsInvisible() { + flicker.assertLayers { + this.isVisible(pipApp) + .then() + .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isInvisible(pipApp) + .isVisible(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 + ) + + /** Checks the [pipApp] task become invisible after transition finish. */ + @Presubmit @Test fun pipAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(pipApp) + + /** Checks the [pipApp] task is in split screen bounds when transition start. */ + @Presubmit + @Test + fun pipAppBoundsIsVisibleAtBegin() = + flicker.assertLayersStart { + this.splitAppLayerBoundsSnapToDivider( + pipApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true, + flicker.scenario.startRotation + ) + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + ) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt index 676c150815ad..f3145c97a6f1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt @@ -17,19 +17,23 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.tools.common.NavBar +import android.tools.common.flicker.subject.layers.LayersTraceSubject import android.tools.common.flicker.subject.region.RegionSubject +import android.tools.common.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.ICommonAssertions -import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.layerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark +import com.android.wm.shell.flicker.utils.ICommonAssertions +import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT +import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -37,26 +41,42 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test unlocking insecure keyguard to back to split screen tasks and verify the transition behavior. + * Test unlocking insecure keyguard to back to split screen tasks and verify the transition + * behavior. * - * To run this test: `atest WMShellFlickerTests:UnlockKeyguardToSplitScreen` + * To run this test: `atest WMShellFlickerTestsSplitScreen:UnlockKeyguardToSplitScreen` */ @RequiresDevice @Postsubmit @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) : - UnlockKeyguardToSplitScreenBenchmark(flicker), ICommonAssertions { +class UnlockKeyguardToSplitScreen(override val flicker: LegacyFlickerTest) : + UnlockKeyguardToSplitScreenBenchmark(flicker), ICommonAssertions { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { - defaultSetup(this) defaultTeardown(this) thisTransition(this) } @Test + @FlakyTest(bugId = 293578017) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + // TODO(b/293578017) remove once that bug is resolve + @Test + @Presubmit + fun visibleLayersShownMoreThanOneConsecutiveEntry_withoutWallpaper() = + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + + listOf(WALLPAPER_BBQ_WRAPPER) + ) + } + + @Test fun splitScreenDividerIsVisibleAtEnd() { flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } } @@ -65,33 +85,35 @@ class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) : @Test fun primaryAppBoundsIsVisibleAtEnd() = - flicker.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, - landscapePosLeft = false, - portraitPosTop = false - ) + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = false, + portraitPosTop = false + ) @Test fun secondaryAppBoundsIsVisibleAtEnd() = - flicker.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, - landscapePosLeft = true, - portraitPosTop = true - ) + flicker.splitAppLayerBoundsIsVisibleAtEnd( + secondaryApp, + landscapePosLeft = true, + portraitPosTop = true + ) - @Test - fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp) + @Test fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp) - @Test - fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp) + @Test fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp) @Test fun notOverlapsForPrimaryAndSecondaryAppLayers() { flicker.assertLayers { this.invoke("notOverlapsForPrimaryAndSecondaryLayers") { - val primaryAppRegions = it.subjects.filter { subject -> - subject.name.contains(primaryApp.toLayerName()) && subject.isVisible - }.mapNotNull { primaryApp -> primaryApp.layer.visibleRegion }.toTypedArray() + val primaryAppRegions = + it.subjects + .filter { subject -> + subject.name.contains(primaryApp.toLayerName()) && subject.isVisible + } + .mapNotNull { primaryApp -> primaryApp.layer.visibleRegion } + .toTypedArray() val primaryAppRegionArea = RegionSubject(primaryAppRegions, it.timestamp) it.visibleRegion(secondaryApp).notOverlaps(primaryAppRegionArea.region) @@ -102,10 +124,9 @@ class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index c3c5f88eaa29..4d9007093cea 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt @@ -16,18 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) : +abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val textEditApp = SplitScreenUtils.getIme(instrumentation) protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#") @@ -54,26 +50,9 @@ open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) : } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - open fun cujCompleted() { - // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit() - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt index 37cd18fad521..8360e94a6c3e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt @@ -16,18 +16,13 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenDismissed -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +31,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScreenBase(flicker) { +abstract class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) : + SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } @@ -60,24 +56,9 @@ open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScr } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt index 0ec6dc96bcd9..e74587843a72 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt @@ -16,18 +16,13 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenDismissed -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +31,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest) : +abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -47,24 +42,9 @@ open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt index 190e2e765bcc..c3beb366cc66 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt @@ -16,19 +16,15 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils 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 @@ -37,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) : +abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -45,32 +41,14 @@ open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) : transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - @Before fun before() { Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - open fun cujCompleted() { - // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is - // robust enough to get the correct end state. - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt index 3a1d1a4415c3..394864ad9d4d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt @@ -16,21 +16,16 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils 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 @@ -39,7 +34,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: FlickerTest) : +abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit @@ -52,43 +47,23 @@ open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: Flic tapl.launchedAppState.taskbar .openAllApps() .getAppIcon(secondaryApp.appName) - .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName) SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - @Before fun before() { Assume.assumeTrue(tapl.isTablet) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered( - primaryApp, - secondaryApp, - fromOtherApp = false, - appExistAtStart = false - ) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt index 2033b7d64416..cd3fbab1497b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt @@ -16,21 +16,16 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils 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 @@ -39,8 +34,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: FlickerTest) : - SplitScreenBase(flicker) { +abstract class EnterSplitScreenByDragFromNotificationBenchmark( + override val flicker: LegacyFlickerTest +) : SplitScreenBase(flicker) { protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -58,21 +54,6 @@ open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: teardown { sendNotificationApp.exit(wmHelper) } } - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false) - @Before fun before() { Assume.assumeTrue(tapl.isTablet) @@ -81,11 +62,10 @@ open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt index b7a7110afe25..3b3be84f9841 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt @@ -16,21 +16,16 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils 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 @@ -39,8 +34,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) : - SplitScreenBase(flicker) { +abstract class EnterSplitScreenByDragFromShortcutBenchmark( + override val flicker: LegacyFlickerTest +) : SplitScreenBase(flicker) { @Before fun before() { Assume.assumeTrue(tapl.isTablet) @@ -57,38 +53,18 @@ open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) : .getAppIcon(secondaryApp.appName) .openDeepShortcutMenu() .getMenuItem("Split Screen Secondary Activity") - .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName) SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered( - primaryApp, - secondaryApp, - fromOtherApp = false, - appExistAtStart = false - ) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt index b1ce62f99e6c..eff355987cc0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt @@ -16,21 +16,16 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils 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 @@ -39,7 +34,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: FlickerTest) : +abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -51,31 +46,11 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Flic transitions { tapl.launchedAppState.taskbar .getAppIcon(secondaryApp.appName) - .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName) SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered( - primaryApp, - secondaryApp, - fromOtherApp = false, - appExistAtStart = false - ) - @Before fun before() { Assume.assumeTrue(tapl.isTablet) @@ -84,10 +59,9 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Flic companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt index 14f07453b7d1..be507d833986 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt @@ -16,18 +16,13 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +31,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTest) : +abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -56,24 +51,9 @@ open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTe } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.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/benchmark/SplitScreenBase.kt index 195b73a14a72..a0e437c25aa7 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/benchmark/SplitScreenBase.kt @@ -14,15 +14,16 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.splitscreen +package com.android.wm.shell.flicker.splitscreen.benchmark import android.content.Context import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.BaseBenchmarkTest +import com.android.wm.shell.flicker.utils.SplitScreenUtils -abstract class SplitScreenBase(flicker: FlickerTest) : BaseBenchmarkTest(flicker) { +abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) { protected val context: Context = instrumentation.context protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation) protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) @@ -32,7 +33,10 @@ abstract class SplitScreenBase(flicker: FlickerTest) : BaseBenchmarkTest(flicker tapl.setEnableRotation(true) setRotation(flicker.scenario.startRotation) tapl.setExpectedRotation(flicker.scenario.startRotation.value) - tapl.workspace.switchToOverview().dismissAllTasks() + val overview = tapl.workspace.switchToOverview() + if (overview.hasTasks()) { + overview.dismissAllTasks() + } } } @@ -42,11 +46,4 @@ abstract class SplitScreenBase(flicker: FlickerTest) : BaseBenchmarkTest(flicker secondaryApp.exit(wmHelper) } } - - protected open val withoutTracing: FlickerBuilder.() -> Unit = { - withoutLayerTracing() - withoutWindowManagerTracing() - withoutTransitionTracing() - withoutTransactionsTracing() - } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index 65fb1358a9b0..ed0debd01408 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt @@ -16,21 +16,17 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,7 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTest) : +abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -53,14 +49,6 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTes } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) { wmHelper .StateSyncBuilder() @@ -134,22 +122,13 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTes return displayBounds.width > displayBounds.height } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - open fun cujCompleted() { - // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is - // robust enough to get the correct end state. - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt index b333aba447a2..9b7939a3a006 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt @@ -16,19 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: FlickerTest) : +abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) @@ -55,27 +50,13 @@ open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Flicke } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt index a27540efdad7..9326ef3024a4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt @@ -16,19 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) : +abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -53,27 +48,13 @@ open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt index 18bf4ff054e0..b928e40108bf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt @@ -16,19 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTest) : +abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -53,27 +48,13 @@ open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTes } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt index c5fe61e26733..f314995fa947 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt @@ -16,17 +16,13 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -35,7 +31,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) : +abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thirdApp = SplitScreenUtils.getIme(instrumentation) protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) @@ -64,14 +60,9 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit @Test open fun cujCompleted() {} - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt index 5f16e5b4d65e..e71834de7123 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt @@ -19,11 +19,10 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -33,11 +32,11 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: FlickerTest) : - SplitScreenBase(flicker) { +abstract class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) : + SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { - setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } + setup { SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp) } transitions { device.sleep() wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() @@ -47,21 +46,12 @@ open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: FlickerTes } } - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } -}
\ No newline at end of file +} 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/utils/CommonAssertions.kt index 798cc95c020f..f1cb37ee1293 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/utils/CommonAssertions.kt @@ -16,25 +16,25 @@ @file:JvmName("CommonAssertions") -package com.android.wm.shell.flicker +package com.android.wm.shell.flicker.utils import android.tools.common.Rotation import android.tools.common.datatypes.Region import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject import android.tools.common.flicker.subject.layers.LayersTraceSubject import android.tools.common.traces.component.IComponentMatcher -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.helpers.WindowUtils -fun FlickerTest.appPairsDividerIsVisibleAtEnd() { +fun LegacyFlickerTest.appPairsDividerIsVisibleAtEnd() { assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) } } -fun FlickerTest.appPairsDividerIsInvisibleAtEnd() { +fun LegacyFlickerTest.appPairsDividerIsInvisibleAtEnd() { assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) } } -fun FlickerTest.appPairsDividerBecomesVisible() { +fun LegacyFlickerTest.appPairsDividerBecomesVisible() { assertLayers { this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() @@ -42,7 +42,7 @@ fun FlickerTest.appPairsDividerBecomesVisible() { } } -fun FlickerTest.splitScreenEntered( +fun LegacyFlickerTest.splitScreenEntered( component1: IComponentMatcher, component2: IComponentMatcher, fromOtherApp: Boolean, @@ -69,7 +69,7 @@ fun FlickerTest.splitScreenEntered( splitScreenDividerIsVisibleAtEnd() } -fun FlickerTest.splitScreenDismissed( +fun LegacyFlickerTest.splitScreenDismissed( component1: IComponentMatcher, component2: IComponentMatcher, toHome: Boolean @@ -87,27 +87,27 @@ fun FlickerTest.splitScreenDismissed( splitScreenDividerIsInvisibleAtEnd() } -fun FlickerTest.splitScreenDividerIsVisibleAtStart() { +fun LegacyFlickerTest.splitScreenDividerIsVisibleAtStart() { assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } } -fun FlickerTest.splitScreenDividerIsVisibleAtEnd() { +fun LegacyFlickerTest.splitScreenDividerIsVisibleAtEnd() { assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } } -fun FlickerTest.splitScreenDividerIsInvisibleAtStart() { +fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtStart() { assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } } -fun FlickerTest.splitScreenDividerIsInvisibleAtEnd() { +fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtEnd() { assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } } -fun FlickerTest.splitScreenDividerBecomesVisible() { +fun LegacyFlickerTest.splitScreenDividerBecomesVisible() { layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } -fun FlickerTest.splitScreenDividerBecomesInvisible() { +fun LegacyFlickerTest.splitScreenDividerBecomesInvisible() { assertLayers { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) .then() @@ -115,23 +115,23 @@ fun FlickerTest.splitScreenDividerBecomesInvisible() { } } -fun FlickerTest.layerBecomesVisible(component: IComponentMatcher) { +fun LegacyFlickerTest.layerBecomesVisible(component: IComponentMatcher) { assertLayers { this.isInvisible(component).then().isVisible(component) } } -fun FlickerTest.layerBecomesInvisible(component: IComponentMatcher) { +fun LegacyFlickerTest.layerBecomesInvisible(component: IComponentMatcher) { assertLayers { this.isVisible(component).then().isInvisible(component) } } -fun FlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) { +fun LegacyFlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) { assertLayersEnd { this.isVisible(component) } } -fun FlickerTest.layerKeepVisible(component: IComponentMatcher) { +fun LegacyFlickerTest.layerKeepVisible(component: IComponentMatcher) { assertLayers { this.isVisible(component) } } -fun FlickerTest.splitAppLayerBoundsBecomesVisible( +fun LegacyFlickerTest.splitAppLayerBoundsBecomesVisible( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean @@ -150,7 +150,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesVisible( } } -fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) { +fun LegacyFlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) { assertLayers { this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true) .then() @@ -161,7 +161,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMat } } -fun FlickerTest.splitAppLayerBoundsBecomesInvisible( +fun LegacyFlickerTest.splitAppLayerBoundsBecomesInvisible( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean @@ -180,7 +180,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesInvisible( } } -fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.splitAppLayerBoundsIsVisibleAtEnd( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean @@ -195,7 +195,7 @@ fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd( } } -fun FlickerTest.splitAppLayerBoundsKeepVisible( +fun LegacyFlickerTest.splitAppLayerBoundsKeepVisible( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean @@ -210,7 +210,7 @@ fun FlickerTest.splitAppLayerBoundsKeepVisible( } } -fun FlickerTest.splitAppLayerBoundsChanges( +fun LegacyFlickerTest.splitAppLayerBoundsChanges( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean @@ -265,6 +265,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region ?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found") + visibleRegion(component).isNotEmpty() visibleRegion(component) .coversAtMost( if (displayBounds.width > displayBounds.height) { @@ -304,7 +305,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( } } -fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowBecomesVisible(component: IComponentMatcher) { assertWm { this.isAppWindowInvisible(component) .then() @@ -316,39 +317,39 @@ fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) { } } -fun FlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) { assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) } } -fun FlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) { assertWmStart { this.isAppWindowVisible(component) } } -fun FlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) { assertWmEnd { this.isAppWindowVisible(component) } } -fun FlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) { assertWmStart { this.isAppWindowInvisible(component) } } -fun FlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) { assertWmEnd { this.isAppWindowInvisible(component) } } -fun FlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) { assertWmStart { this.notContains(component) } } -fun FlickerTest.appWindowKeepVisible(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowKeepVisible(component: IComponentMatcher) { assertWm { this.isAppWindowVisible(component) } } -fun FlickerTest.dockedStackDividerIsVisibleAtEnd() { +fun LegacyFlickerTest.dockedStackDividerIsVisibleAtEnd() { assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) } } -fun FlickerTest.dockedStackDividerBecomesVisible() { +fun LegacyFlickerTest.dockedStackDividerBecomesVisible() { assertLayers { this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() @@ -356,7 +357,7 @@ fun FlickerTest.dockedStackDividerBecomesVisible() { } } -fun FlickerTest.dockedStackDividerBecomesInvisible() { +fun LegacyFlickerTest.dockedStackDividerBecomesInvisible() { assertLayers { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() @@ -364,11 +365,11 @@ fun FlickerTest.dockedStackDividerBecomesInvisible() { } } -fun FlickerTest.dockedStackDividerNotExistsAtEnd() { +fun LegacyFlickerTest.dockedStackDividerNotExistsAtEnd() { assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) } } -fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd( rotation: Rotation, primaryComponent: IComponentMatcher ) { @@ -380,7 +381,7 @@ fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd( } } -fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( rotation: Rotation, primaryComponent: IComponentMatcher ) { @@ -392,7 +393,7 @@ fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( } } -fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd( rotation: Rotation, secondaryComponent: IComponentMatcher ) { @@ -404,7 +405,7 @@ fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd( } } -fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd( rotation: Rotation, secondaryComponent: IComponentMatcher ) { 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/utils/CommonConstants.kt index 3bc1e2acd015..3b66d6addacd 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/utils/CommonConstants.kt @@ -16,7 +16,7 @@ @file:JvmName("CommonConstants") -package com.android.wm.shell.flicker +package com.android.wm.shell.flicker.utils import android.tools.common.traces.component.ComponentNameMatcher diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt index 02d9a056afbf..7f58cedce63d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.wm.shell.flicker +package com.android.wm.shell.flicker.utils import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd @@ -32,7 +32,7 @@ import org.junit.Assume import org.junit.Test interface ICommonAssertions { - val flicker: FlickerTest + val flicker: LegacyFlickerTest /** Checks that all parts of the screen are covered during the transition */ @Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered() 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/utils/MultiWindowUtils.kt index 87b94ff8668b..9b3a480c06b1 100644 --- 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/utils/MultiWindowUtils.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker +package com.android.wm.shell.flicker.utils import android.app.Instrumentation import android.content.Context 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/utils/NotificationListener.kt index e0ef92457f58..529c1254a64c 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/utils/NotificationListener.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker +package com.android.wm.shell.flicker.utils import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification 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/utils/SplitScreenUtils.kt index 1063dfd8d737..3f8a1ae6bd79 100644 --- 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/utils/SplitScreenUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.splitscreen +package com.android.wm.shell.flicker.utils import android.app.Instrumentation import android.graphics.Point @@ -39,11 +39,10 @@ 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.testapp.ActivityOptions -import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME -import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME +import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary import org.junit.Assert.assertNotNull -internal object SplitScreenUtils { +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" @@ -112,6 +111,16 @@ internal object SplitScreenUtils { waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } + fun enterSplitViaIntent( + wmHelper: WindowManagerStateHelper, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true") + primaryApp.launchViaIntent(wmHelper, null, null, stringExtras) + 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 @@ -314,14 +323,14 @@ internal object SplitScreenUtils { dividerBar.drag( Point( if (dragToRight) { - displayBounds.width * 4 / 5 + displayBounds.right } else { - displayBounds.width * 1 / 5 + displayBounds.left }, if (dragToBottom) { - displayBounds.height * 4 / 5 + displayBounds.bottom } else { - displayBounds.height * 1 / 5 + displayBounds.top } ) ) 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/utils/WaitUtils.kt index 556cb06f3ca1..cf2df4e17cb7 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/utils/WaitUtils.kt @@ -16,7 +16,7 @@ @file:JvmName("WaitUtils") -package com.android.wm.shell.flicker +package com.android.wm.shell.flicker.utils import android.os.SystemClock diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto new file mode 100644 index 000000000000..406ada97a07d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto @@ -0,0 +1,75 @@ +# 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. + +# proto-message: TraceConfig + +# Enable periodic flushing of the trace buffer into the output file. +write_into_file: true + +# Writes the userspace buffer into the file every 1s. +file_write_period_ms: 2500 + +# See b/126487238 - we need to guarantee ordering of events. +flush_period_ms: 30000 + +# The trace buffers needs to be big enough to hold |file_write_period_ms| of +# trace data. The trace buffer sizing depends on the number of trace categories +# enabled and the device activity. + +# RSS events +buffers: { + size_kb: 63488 + fill_policy: RING_BUFFER +} + +data_sources { + config { + name: "linux.process_stats" + target_buffer: 0 + # polled per-process memory counters and process/thread names. + # If you don't want the polled counters, remove the "process_stats_config" + # section, but keep the data source itself as it still provides on-demand + # thread/process naming for ftrace data below. + process_stats_config { + scan_all_processes_on_start: true + } + } +} + +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + ftrace_events: "ftrace/print" + ftrace_events: "task/task_newtask" + ftrace_events: "task/task_rename" + atrace_categories: "ss" + atrace_categories: "wm" + atrace_categories: "am" + atrace_categories: "aidl" + atrace_categories: "input" + atrace_categories: "binder_driver" + atrace_categories: "sched_process_exit" + atrace_apps: "com.android.server.wm.flicker.testapp" + atrace_apps: "com.android.systemui" + atrace_apps: "com.android.wm.shell.flicker" + atrace_apps: "com.android.wm.shell.flicker.other" + atrace_apps: "com.android.wm.shell.flicker.bubbles" + atrace_apps: "com.android.wm.shell.flicker.pip" + atrace_apps: "com.android.wm.shell.flicker.splitscreen" + atrace_apps: "com.google.android.apps.nexuslauncher" + } + } +} + diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index ad4d97f6fe40..54f94986d90c 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -43,6 +43,7 @@ android_test { "frameworks-base-testutils", "kotlinx-coroutines-android", "kotlinx-coroutines-core", + "mockito-kotlin2", "mockito-target-extended-minus-junit4", "truth-prebuilt", "testables", @@ -61,7 +62,7 @@ android_test { "libstaticjvmtiagent", ], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], plugins: ["dagger2-compiler"], diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java index 8278c67a9b4f..0dc16f44340f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java @@ -64,18 +64,27 @@ public class BubbleOverflowTest extends ShellTestCase { } @Test - public void test_initialize() { + public void test_initialize_forStack() { assertThat(mOverflow.getExpandedView()).isNull(); - mOverflow.initialize(mBubbleController); + mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false); assertThat(mOverflow.getExpandedView()).isNotNull(); assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY); + assertThat(mOverflow.getBubbleBarExpandedView()).isNull(); + } + + @Test + public void test_initialize_forBubbleBar() { + mOverflow.initialize(mBubbleController, /* forBubbleBar= */ true); + + assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull(); + assertThat(mOverflow.getExpandedView()).isNull(); } @Test public void test_cleanUpExpandedState() { - mOverflow.createExpandedView(); + mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false); assertThat(mOverflow.getExpandedView()).isNotNull(); mOverflow.cleanUpExpandedState(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java index 09d474d1f97c..a97c19f17412 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.desktopmode; +package com.android.wm.shell; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -25,16 +25,16 @@ import android.window.WindowContainerToken; /** * {@link WindowContainerToken} wrapper that supports a mock binder */ -class MockToken { +public class MockToken { private final WindowContainerToken mToken; - MockToken() { + public MockToken() { mToken = mock(WindowContainerToken.class); IBinder binder = mock(IBinder.class); when(mToken.asBinder()).thenReturn(binder); } - WindowContainerToken token() { + public WindowContainerToken token() { return mToken; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index 4fca8b46a069..2d9304705738 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -92,7 +92,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim .build(); final Animator animator = mAnimRunner.createAnimator( info, mStartTransaction, mFinishTransaction, - () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */), + () -> mFinishCallback.onTransitionFinished(null /* wct */), new ArrayList()); // The animation should be empty when it is behind starting window. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java index ab1ccd4599a2..0b2265d4ce9c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java @@ -75,7 +75,7 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { assertNotNull(mAnimRunner); mAnimSpec = mAnimRunner.mAnimationSpec; assertNotNull(mAnimSpec); - mFinishCallback = (wct, wctCB) -> {}; + mFinishCallback = (wct) -> {}; spyOn(mController); spyOn(mAnimRunner); spyOn(mAnimSpec); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index ba34f1f74cd3..270dbc49835f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -217,12 +217,10 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation doReturn(animator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any()); mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback); - verify(mFinishCallback, never()).onTransitionFinished(any(), any()); + verify(mFinishCallback, never()).onTransitionFinished(any()); mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(), - mTransition, - (wct, cb) -> { - }); - verify(mFinishCallback).onTransitionFinished(any(), any()); + mTransition, (wct) -> {}); + verify(mFinishCallback).onTransitionFinished(any()); } @Test @@ -238,9 +236,9 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback); - verify(mFinishCallback, never()).onTransitionFinished(any(), any()); + verify(mFinishCallback, never()).onTransitionFinished(any()); mController.onAnimationFinished(mTransition); - verify(mFinishCallback).onTransitionFinished(any(), any()); + verify(mFinishCallback).onTransitionFinished(any()); // Should not call finish when the finish has already been called. assertThrows(IllegalStateException.class, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt index 17ed396987af..e7274918fa2b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt @@ -424,6 +424,7 @@ class PhysicsAnimatorTest : ShellTestCase() { eq(-5f), anyFloat(), eq(true)) } + @Ignore("Started flaking despite no changes, tracking in b/299636216") @Test fun testIsPropertyAnimating() { PhysicsAnimatorTestUtils.setAllAnimationsBlock(false) 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 3d8bd3854a45..e7d0f601ff5a 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 @@ -67,6 +67,7 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.sysui.ShellSharedConstants; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -85,12 +86,11 @@ public class BackAnimationControllerTest extends ShellTestCase { private static final String ANIMATION_ENABLED = "1"; private final TestShellExecutor mShellExecutor = new TestShellExecutor(); - private ShellInit mShellInit; - @Rule public TestableContext mContext = new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()); + private ShellInit mShellInit; @Mock private IActivityTaskManager mActivityTaskManager; @@ -116,6 +116,8 @@ public class BackAnimationControllerTest extends ShellTestCase { private TestableContentResolver mContentResolver; private TestableLooper mTestableLooper; + private ShellBackAnimationRegistry mShellBackAnimationRegistry; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -126,11 +128,23 @@ public class BackAnimationControllerTest extends ShellTestCase { ANIMATION_ENABLED); mTestableLooper = TestableLooper.get(this); mShellInit = spy(new ShellInit(mShellExecutor)); - mController = new BackAnimationController(mShellInit, mShellController, - mShellExecutor, new Handler(mTestableLooper.getLooper()), - mActivityTaskManager, mContext, - mContentResolver, mAnimationBackground); - mController.setEnableUAnimation(true); + mShellBackAnimationRegistry = + new ShellBackAnimationRegistry( + new CrossActivityAnimation(mContext, mAnimationBackground), + new CrossTaskBackAnimation(mContext, mAnimationBackground), + new CustomizeActivityAnimation(mContext, mAnimationBackground), + null); + mController = + new BackAnimationController( + mShellInit, + mShellController, + mShellExecutor, + new Handler(mTestableLooper.getLooper()), + mActivityTaskManager, + mContext, + mContentResolver, + mAnimationBackground, + mShellBackAnimationRegistry); mShellInit.init(); mShellExecutor.flushAll(); } @@ -138,12 +152,13 @@ public class BackAnimationControllerTest extends ShellTestCase { private void createNavigationInfo(int backType, boolean enableAnimation, boolean isAnimationCallback) { - BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() - .setType(backType) - .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) - .setOnBackInvokedCallback(mAppCallback) - .setPrepareRemoteAnimation(enableAnimation) - .setAnimationCallback(isAnimationCallback); + BackNavigationInfo.Builder builder = + new BackNavigationInfo.Builder() + .setType(backType) + .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) + .setOnBackInvokedCallback(mAppCallback) + .setPrepareRemoteAnimation(enableAnimation) + .setAnimationCallback(isAnimationCallback); createNavigationInfo(builder); } @@ -188,18 +203,21 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test 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) { + 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); } - for (int type: testTypes) { - final ResultListener result = new ResultListener(); + for (int type : testTypes) { + final ResultListener result = new ResultListener(); createNavigationInfo(new BackNavigationInfo.Builder() .setType(type) .setOnBackInvokedCallback(mAppCallback) @@ -275,10 +293,17 @@ public class BackAnimationControllerTest extends ShellTestCase { // Toggle the setting off 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()), - mActivityTaskManager, mContext, - mContentResolver, mAnimationBackground); + mController = + new BackAnimationController( + shellInit, + mShellController, + mShellExecutor, + new Handler(mTestableLooper.getLooper()), + mActivityTaskManager, + mContext, + mContentResolver, + mAnimationBackground, + mShellBackAnimationRegistry); shellInit.init(); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); @@ -398,17 +423,19 @@ public class BackAnimationControllerTest extends ShellTestCase { @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) { + 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) { + for (int type : testTypes) { final ResultListener result = new ResultListener(); createNavigationInfo(new BackNavigationInfo.Builder() .setType(type) @@ -468,16 +495,14 @@ public class BackAnimationControllerTest extends ShellTestCase { public void testBackToActivity() throws RemoteException { final CrossActivityAnimation animation = new CrossActivityAnimation(mContext, mAnimationBackground); - verifySystemBackBehavior( - BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner); + verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner()); } @Test public void testBackToTask() throws RemoteException { final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext, mAnimationBackground); - verifySystemBackBehavior( - BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner); + verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK, animation.getRunner()); } private void verifySystemBackBehavior(int type, BackAnimationRunner animation) @@ -554,10 +579,12 @@ public class BackAnimationControllerTest extends ShellTestCase { 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/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java index e7d459893ce8..cebbbd890f05 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java @@ -102,15 +102,17 @@ public class CustomizeActivityAnimationTest extends ShellTestCase { // start animation with remote animation targets final CountDownLatch finishCalled = new CountDownLatch(1); final Runnable finishCallback = finishCalled::countDown; - mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation( - new RemoteAnimationTarget[]{close, open}, null, null, finishCallback); + mCustomizeActivityAnimation + .getRunner() + .startAnimation( + new RemoteAnimationTarget[] {close, open}, null, null, finishCallback); verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE)); verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE)); try { - mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked(); + mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked(); } catch (RemoteException r) { fail("onBackInvoked throw remote exception"); } @@ -133,15 +135,17 @@ public class CustomizeActivityAnimationTest extends ShellTestCase { // start animation with remote animation targets final CountDownLatch finishCalled = new CountDownLatch(1); final Runnable finishCallback = finishCalled::countDown; - mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation( - new RemoteAnimationTarget[]{close, open}, null, null, finishCallback); + mCustomizeActivityAnimation + .getRunner() + .startAnimation( + new RemoteAnimationTarget[] {close, open}, null, null, finishCallback); verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE)); verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE)); try { - mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackCancelled(); + mCustomizeActivityAnimation.getRunner().getCallback().onBackCancelled(); } catch (RemoteException r) { fail("onBackCancelled throw remote exception"); } @@ -155,11 +159,12 @@ public class CustomizeActivityAnimationTest extends ShellTestCase { // start animation without any remote animation targets final CountDownLatch finishCalled = new CountDownLatch(1); final Runnable finishCallback = finishCalled::countDown; - mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation( - new RemoteAnimationTarget[]{}, null, null, finishCallback); + mCustomizeActivityAnimation + .getRunner() + .startAnimation(new RemoteAnimationTarget[] {}, null, null, finishCallback); try { - mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked(); + mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked(); } catch (RemoteException r) { fail("onBackInvoked throw remote exception"); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt new file mode 100644 index 000000000000..e35995775f76 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles + +import android.app.ActivityTaskManager +import android.content.pm.LauncherApps +import android.os.Handler +import android.os.Looper +import android.util.SparseArray +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.bubbles.storage.BubbleEntity +import com.android.wm.shell.bubbles.storage.BubblePersistentRepository +import com.android.wm.shell.common.HandlerExecutor +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + +class BubbleDataRepositoryTest : ShellTestCase() { + + private val user0BubbleEntities = listOf( + BubbleEntity( + userId = 0, + packageName = "com.example.messenger", + shortcutId = "shortcut-1", + key = "0k1", + desiredHeight = 120, + desiredHeightResId = 0, + title = null, + taskId = 1, + locus = null, + isDismissable = true + ), + BubbleEntity( + userId = 10, + packageName = "com.example.chat", + shortcutId = "alice and bob", + key = "0k2", + desiredHeight = 0, + desiredHeightResId = 16537428, + title = "title", + taskId = 2, + locus = null + ), + BubbleEntity( + userId = 0, + packageName = "com.example.messenger", + shortcutId = "shortcut-2", + key = "0k3", + desiredHeight = 120, + desiredHeightResId = 0, + title = null, + taskId = ActivityTaskManager.INVALID_TASK_ID, + locus = null + ) + ) + + private val user1BubbleEntities = listOf( + BubbleEntity( + userId = 1, + packageName = "com.example.messenger", + shortcutId = "shortcut-1", + key = "1k1", + desiredHeight = 120, + desiredHeightResId = 0, + title = null, + taskId = 3, + locus = null, + isDismissable = true + ), + BubbleEntity( + userId = 12, + packageName = "com.example.chat", + shortcutId = "alice and bob", + key = "1k2", + desiredHeight = 0, + desiredHeightResId = 16537428, + title = "title", + taskId = 4, + locus = null + ), + BubbleEntity( + userId = 1, + packageName = "com.example.messenger", + shortcutId = "shortcut-2", + key = "1k3", + desiredHeight = 120, + desiredHeightResId = 0, + title = null, + taskId = ActivityTaskManager.INVALID_TASK_ID, + locus = null + ), + BubbleEntity( + userId = 12, + packageName = "com.example.chat", + shortcutId = "alice", + key = "1k4", + desiredHeight = 0, + desiredHeightResId = 16537428, + title = "title", + taskId = 5, + locus = null + ) + ) + + private val testHandler = Handler(Looper.getMainLooper()) + private val mainExecutor = HandlerExecutor(testHandler) + private val launcherApps = mock<LauncherApps>() + + private val persistedBubbles = SparseArray<List<BubbleEntity>>() + + private lateinit var dataRepository: BubbleDataRepository + private lateinit var persistentRepository: BubblePersistentRepository + + @Before + fun setup() { + persistentRepository = BubblePersistentRepository(mContext) + dataRepository = spy(BubbleDataRepository(launcherApps, mainExecutor, persistentRepository)) + + persistedBubbles.put(0, user0BubbleEntities) + persistedBubbles.put(1, user1BubbleEntities) + } + + @After + fun teardown() { + // Clean up any persisted bubbles for the next run + persistentRepository.persistsToDisk(SparseArray()) + } + + @Test + fun testFilterForActiveUsersAndPersist_allValid() { + // Matches all the users in user0BubbleEntities & user1BubbleEntities + val activeUserIds = listOf(0, 10, 1, 12) + + val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist( + activeUserIds, persistedBubbles) + + // No invalid users, so no changes + assertThat(persistedBubbles).isEqualTo(validEntitiesByUser) + + // No invalid users, so no persist to disk happened + verify(dataRepository, never()).persistToDisk(any()) + } + + @Test + fun testFilterForActiveUsersAndPersist_invalidParent() { + // When we start, we do have user 0 bubbles. + assertThat(persistedBubbles.get(0)).isNotEmpty() + + val activeUserIds = listOf(10, 1, 12) // Missing user 0 + val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist( + activeUserIds, persistedBubbles) + + // We no longer have any user 0 bubbles. + assertThat(validEntitiesByUser.get(0)).isNull() + // User 1 bubbles should be the same. + assertThat(validEntitiesByUser.get(1)).isEqualTo(user1BubbleEntities) + + // Verify that persist to disk happened with the new valid entities list. + verify(dataRepository).persistToDisk(validEntitiesByUser) + } + + @Test + fun testFilterForActiveUsersAndPersist_invalidChild() { + // Build a list to compare against (remove all user 12 bubbles) + val (user1EntitiesWithUser12, user1EntitiesWithoutUser12) = + user1BubbleEntities.partition { it.userId == 12 } + + // Verify we start with user 12 bubbles + assertThat(persistedBubbles.get(1).containsAll(user1EntitiesWithUser12)).isTrue() + + val activeUserIds = listOf(0, 10, 1) // Missing user 1's child user 12 + val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist( + activeUserIds, persistedBubbles) + + // We no longer have any user 12 bubbles. + assertThat(validEntitiesByUser.get(1)).isEqualTo(user1EntitiesWithoutUser12) + + // Verify that persist to disk happened with the new valid entities list. + verify(dataRepository).persistToDisk(validEntitiesByUser) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 4a55429eacb6..26c73946c1c6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -111,6 +111,8 @@ public class BubbleDataTest extends ShellTestCase { @Mock private BubbleLogger mBubbleLogger; @Mock + private BubbleEducationController mEducationController; + @Mock private ShellExecutor mMainExecutor; @Captor @@ -191,7 +193,7 @@ public class BubbleDataTest extends ShellTestCase { mPositioner = new TestableBubblePositioner(mContext, mock(WindowManager.class)); - mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, + mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController, mMainExecutor); // Used by BubbleData to set lastAccessedTime @@ -385,6 +387,65 @@ public class BubbleDataTest extends ShellTestCase { assertOverflowChangedTo(ImmutableList.of()); } + /** + * Verifies that the update shouldn't show the user education, if the education is not required + */ + @Test + public void test_shouldNotShowEducation() { + // Setup + when(mEducationController.shouldShowStackEducation(any())).thenReturn(false); + mBubbleData.setListener(mListener); + + // Test + mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */ + true); + + // Verify + verifyUpdateReceived(); + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.shouldShowEducation).isFalse(); + } + + /** + * Verifies that the update should show the user education, if the education is required + */ + @Test + public void test_shouldShowEducation() { + // Setup + when(mEducationController.shouldShowStackEducation(any())).thenReturn(true); + mBubbleData.setListener(mListener); + + // Test + mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */ + true); + + // Verify + verifyUpdateReceived(); + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.shouldShowEducation).isTrue(); + } + + /** + * Verifies that the update shouldn't show the user education, if the education is required but + * the bubble should auto-expand + */ + @Test + public void test_shouldShowEducation_shouldAutoExpand() { + // Setup + when(mEducationController.shouldShowStackEducation(any())).thenReturn(true); + mBubbleData.setListener(mListener); + mBubbleA1.setShouldAutoExpand(true); + + // Test + mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */ + true); + + // Verify + verifyUpdateReceived(); + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.shouldShowEducation).isFalse(); + } + // COLLAPSED / ADD /** diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java new file mode 100644 index 000000000000..58d9a6486ff2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.View.LAYOUT_DIRECTION_LTR; +import static android.view.View.LAYOUT_DIRECTION_RTL; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.res.Configuration; +import android.graphics.Insets; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableResources; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests operations and the resulting state managed by {@link BubblePositioner}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class BubblePositionerTest extends ShellTestCase { + + private static final int MIN_WIDTH_FOR_TABLET = 600; + + private BubblePositioner mPositioner; + private Configuration mConfiguration; + + @Mock + private WindowManager mWindowManager; + @Mock + private WindowMetrics mWindowMetrics; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mConfiguration = spy(new Configuration()); + TestableResources testableResources = mContext.getOrCreateTestableResources(); + testableResources.overrideConfiguration(mConfiguration); + + mPositioner = new BubblePositioner(mContext, mWindowManager); + } + + @Test + public void testUpdate() { + Insets insets = Insets.of(10, 20, 5, 15); + Rect screenBounds = new Rect(0, 0, 1000, 1200); + Rect availableRect = new Rect(screenBounds); + availableRect.inset(insets); + + new WindowManagerConfig() + .setInsets(insets) + .setScreenBounds(screenBounds) + .setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect); + assertThat(mPositioner.isLandscape()).isFalse(); + assertThat(mPositioner.isLargeScreen()).isFalse(); + assertThat(mPositioner.getInsets()).isEqualTo(insets); + } + + @Test + public void testShowBubblesVertically_phonePortrait() { + new WindowManagerConfig().setOrientation(ORIENTATION_PORTRAIT).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.showBubblesVertically()).isFalse(); + } + + @Test + public void testShowBubblesVertically_phoneLandscape() { + new WindowManagerConfig().setOrientation(ORIENTATION_LANDSCAPE).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.isLandscape()).isTrue(); + assertThat(mPositioner.showBubblesVertically()).isTrue(); + } + + @Test + public void testShowBubblesVertically_tablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.showBubblesVertically()).isTrue(); + } + + /** If a resting position hasn't been set, calling it will return the default position. */ + @Test + public void testGetRestingPosition_returnsDefaultPosition() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + PointF restingPosition = mPositioner.getRestingPosition(); + PointF defaultPosition = mPositioner.getDefaultStartPosition(); + + assertThat(restingPosition).isEqualTo(defaultPosition); + } + + /** If a resting position has been set, it'll return that instead of the default position. */ + @Test + public void testGetRestingPosition_returnsRestingPosition() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + PointF restingPosition = new PointF(100, 100); + mPositioner.setRestingPosition(restingPosition); + + assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition); + } + + /** Test that the default resting position on phone is in upper left. */ + @Test + public void testGetRestingPosition_bubble_onPhone() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_bubble_onPhone_RTL() { + new WindowManagerConfig().setLayoutDirection(LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + /** Test that the default resting position on tablet is middle left. */ + @Test + public void testGetRestingPosition_chatBubble_onTablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_chatBubble_onTablet_RTL() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + /** Test that the default resting position on tablet is middle right. */ + @Test + public void testGetDefaultPosition_appBubble_onTablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); + + assertThat(startPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(startPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_appBubble_onTablet_RTL() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); + + assertThat(startPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(startPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testHasUserModifiedDefaultPosition_false() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); + + mPositioner.setRestingPosition(mPositioner.getDefaultStartPosition()); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); + } + + @Test + public void testHasUserModifiedDefaultPosition_true() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); + + mPositioner.setRestingPosition(new PointF(0, 100)); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue(); + } + + /** + * Calculates the Y position bubbles should be placed based on the config. Based on + * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and + * {@link BubbleStackView.RelativeStackPosition}. + */ + private float getDefaultYPosition() { + final boolean isTablet = mPositioner.isLargeScreen(); + + // On tablet the position is centered, on phone it is an offset from the top. + final float desiredY = isTablet + ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f) + : mContext.getResources().getDimensionPixelOffset( + R.dimen.bubble_stack_starting_offset_y); + // Since we're visually centering the bubbles on tablet, use total screen height rather + // than the available height. + final float height = isTablet + ? mPositioner.getScreenRect().height() + : mPositioner.getAvailableRect().height(); + float offsetPercent = desiredY / height; + offsetPercent = Math.max(0f, Math.min(1f, offsetPercent)); + final RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent; + } + + /** + * Sets up window manager to return config values based on what you need for the test. + * By default it sets up a portrait phone without any insets. + */ + private class WindowManagerConfig { + private Rect mScreenBounds = new Rect(0, 0, 1000, 2000); + private boolean mIsLargeScreen = false; + private int mOrientation = ORIENTATION_PORTRAIT; + private int mLayoutDirection = LAYOUT_DIRECTION_LTR; + private Insets mInsets = Insets.of(0, 0, 0, 0); + + public WindowManagerConfig setScreenBounds(Rect screenBounds) { + mScreenBounds = screenBounds; + return this; + } + + public WindowManagerConfig setLargeScreen() { + mIsLargeScreen = true; + return this; + } + + public WindowManagerConfig setOrientation(int orientation) { + mOrientation = orientation; + return this; + } + + public WindowManagerConfig setLayoutDirection(int layoutDirection) { + mLayoutDirection = layoutDirection; + return this; + } + + public WindowManagerConfig setInsets(Insets insets) { + mInsets = insets; + return this; + } + + public void setUpConfig() { + mConfiguration.smallestScreenWidthDp = mIsLargeScreen + ? MIN_WIDTH_FOR_TABLET + : MIN_WIDTH_FOR_TABLET - 1; + mConfiguration.orientation = mOrientation; + + when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection); + WindowInsets windowInsets = mock(WindowInsets.class); + when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(mInsets); + when(mWindowMetrics.getWindowInsets()).thenReturn(windowInsets); + when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds); + when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java new file mode 100644 index 000000000000..9655f97cb7cc --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.IWindowContainerToken; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.TransitionInfoBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests of {@link BubblesTransitionObserver}. + */ +@SmallTest +public class BubblesTransitionObserverTest { + + @Mock + private BubbleController mBubbleController; + @Mock + private BubbleData mBubbleData; + + @Mock + private IBinder mTransition; + @Mock + private SurfaceControl.Transaction mStartT; + @Mock + private SurfaceControl.Transaction mFinishT; + + @Mock + private Bubble mBubble; + + private BubblesTransitionObserver mTransitionObserver; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mTransitionObserver = new BubblesTransitionObserver(mBubbleController, mBubbleData); + } + + @Test + public void testOnTransitionReady_open_collapsesStack() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_toFront_collapsesStack() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_noTaskInfo_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + // Null task info + TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, null /* taskInfo */); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_noTaskId_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + // Invalid task id + TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, + createTaskInfo(INVALID_TASK_ID)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_notOpening_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + // Transits that aren't opening + TransitionInfo info = createTransitionInfo(TRANSIT_CHANGE, createTaskInfo(2)); + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + info = createTransitionInfo(TRANSIT_CLOSE, createTaskInfo(3)); + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + info = createTransitionInfo(TRANSIT_TO_BACK, createTaskInfo(4)); + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_stackAnimating_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(true); // Stack is animating + + TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_stackNotExpanded_skip() { + when(mBubbleData.isExpanded()).thenReturn(false); // Stack is not expanded + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_noSelectedBubble_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(null); // No selected bubble + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_openingMatchesExpanded_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + // What's moving to front is same as the opened bubble + TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(1)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + private ActivityManager.RunningTaskInfo createTaskInfo(int taskId) { + final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + return taskInfo; + } + + private TransitionInfo createTransitionInfo(int changeType, + ActivityManager.RunningTaskInfo info) { + final TransitionInfo.Change change = new TransitionInfo.Change( + new WindowContainerToken(mock(IWindowContainerToken.class)), + mock(SurfaceControl.class)); + change.setMode(changeType); + change.setTaskInfo(info); + + return new TransitionInfoBuilder(TRANSIT_OPEN, 0) + .addChange(change).build(); + } + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java new file mode 100644 index 000000000000..d38b848fbb4d --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles.bar; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.graphics.drawable.ColorDrawable; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.core.content.ContextCompat; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class BubbleBarHandleViewTest extends ShellTestCase { + private BubbleBarHandleView mHandleView; + + @Before + public void setup() { + mHandleView = new BubbleBarHandleView(mContext); + } + + @Test + public void testUpdateHandleColor_lightBg() { + mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */); + + assertTrue(mHandleView.getClipToOutline()); + assertTrue(mHandleView.getBackground() instanceof ColorDrawable); + ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground(); + assertEquals(bgDrawable.getColor(), + ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark)); + } + + @Test + public void testUpdateHandleColor_darkBg() { + mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */); + + assertTrue(mHandleView.getClipToOutline()); + assertTrue(mHandleView.getBackground() instanceof ColorDrawable); + ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground(); + assertEquals(bgDrawable.getColor(), + ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light)); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt index 9f0d89bc3128..52375850b9a5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -27,15 +27,13 @@ import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.anyString -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito -import org.mockito.Mockito.mock -import org.mockito.Mockito.never -import org.mockito.Mockito.reset -import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.reset +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions @SmallTest @RunWith(AndroidTestingRunner::class) @@ -66,7 +64,7 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { @Before fun setup() { - launcherApps = mock(LauncherApps::class.java) + launcherApps = mock<LauncherApps>() repository = BubbleVolatileRepository(launcherApps) } @@ -98,7 +96,7 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { repository.addBubbles(user11.identifier, listOf(bubble12)) assertEquals(listOf(bubble11, bubble12), repository.getEntities(user11.identifier)) - Mockito.verifyNoMoreInteractions(launcherApps) + verifyNoMoreInteractions(launcherApps) } @Test @@ -167,9 +165,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { assertThat(ret).isTrue() // bubbles were removed assertThat(repository.getEntities(user0.identifier).toList()).isEmpty() - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) } @Test @@ -184,9 +181,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { assertThat(repository.getEntities(user0.identifier).toList()) .isEqualTo(listOf(bubble1, bubble3)) - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) } @Test @@ -200,9 +196,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { assertThat(repository.getEntities(user0.identifier).toList()) .isEqualTo(listOf(bubble1, bubble2, bubble3)) - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) } @Test @@ -219,9 +214,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { user11.identifier)) assertThat(ret).isFalse() // bubbles were NOT removed - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) } @Test @@ -237,9 +231,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { assertThat(ret).isTrue() // bubbles were removed assertThat(repository.getEntities(user0.identifier).toList()).isEmpty() - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) // User 11 bubbles should still be here assertThat(repository.getEntities(user11.identifier).toList()) @@ -261,9 +254,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { // bubble2 is the work profile bubble and should be removed assertThat(repository.getEntities(user0.identifier).toList()) .isEqualTo(listOf(bubble1, bubble3)) - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) // User 11 bubbles should still be here assertThat(repository.getEntities(user11.identifier).toList()) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt new file mode 100644 index 000000000000..9dc816b65d2e --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt @@ -0,0 +1,172 @@ +/* + * 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.common + +import android.os.IBinder +import android.testing.AndroidTestingRunner +import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp +import androidx.test.filters.SmallTest +import com.android.wm.shell.MockToken +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class LaunchAdjacentControllerTest : ShellTestCase() { + + private lateinit var controller: LaunchAdjacentController + + @Mock private lateinit var syncQueue: SyncTransactionQueue + + @Before + fun setUp() { + controller = LaunchAdjacentController(syncQueue) + } + + @Test + fun newInstance_enabledByDefault() { + assertThat(controller.launchAdjacentEnabled).isTrue() + } + + @Test + fun setLaunchAdjacentRoot_launchAdjacentEnabled_setsFlagRoot() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + val wct = getLatestTransactionOrFail() + assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) + } + + @Test + fun setLaunchAdjacentRoot_launchAdjacentDisabled_doesNotUpdateFlagRoot() { + val token = MockToken().token() + controller.launchAdjacentEnabled = false + controller.setLaunchAdjacentRoot(token) + verify(syncQueue, never()).queue(any()) + } + + @Test + fun clearLaunchAdjacentRoot_launchAdjacentEnabled_clearsFlagRoot() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + controller.clearLaunchAdjacentRoot() + val wct = getLatestTransactionOrFail() + assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) + } + + @Test + fun clearLaunchAdjacentRoot_launchAdjacentDisabled_clearsFlagRoot() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + controller.launchAdjacentEnabled = false + clearInvocations(syncQueue) + + controller.clearLaunchAdjacentRoot() + val wct = getLatestTransactionOrFail() + assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) + } + + @Test + fun setLaunchAdjacentEnabled_wasDisabledWithContainerSet_setsFlagRoot() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + controller.launchAdjacentEnabled = false + clearInvocations(syncQueue) + + controller.launchAdjacentEnabled = true + val wct = getLatestTransactionOrFail() + assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) + } + + @Test + fun setLaunchAdjacentEnabled_containerNotSet_doesNotUpdateFlagRoot() { + controller.launchAdjacentEnabled = false + controller.launchAdjacentEnabled = true + verify(syncQueue, never()).queue(any()) + } + + @Test + fun setLaunchAdjacentEnabled_multipleTimes_setsFlagRootOnce() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + controller.launchAdjacentEnabled = true + controller.launchAdjacentEnabled = true + // Only execute once + verify(syncQueue).queue(any()) + } + + @Test + fun setLaunchAdjacentDisabled_containerSet_clearsFlagRoot() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + controller.launchAdjacentEnabled = false + val wct = getLatestTransactionOrFail() + assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) + } + + @Test + fun setLaunchAdjacentDisabled_containerNotSet_doesNotUpdateFlagRoot() { + controller.launchAdjacentEnabled = false + verify(syncQueue, never()).queue(any()) + } + + @Test + fun setLaunchAdjacentDisabled_multipleTimes_setsFlagRootOnce() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + clearInvocations(syncQueue) + controller.launchAdjacentEnabled = false + controller.launchAdjacentEnabled = false + // Only execute once + verify(syncQueue).queue(any()) + } + + private fun getLatestTransactionOrFail(): WindowContainerTransaction { + val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(syncQueue, atLeastOnce()).queue(arg.capture()) + return arg.allValues.last().also { assertThat(it).isNotNull() } + } +} + +private fun WindowContainerTransaction.getSetLaunchAdjacentFlagRootContainer(): IBinder { + return hierarchyOps + // Find the operation with the correct type + .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT } + // For set flag root operation, toTop is false + .filter { op -> !op.toTop } + .map { it.container } + .first() +} + +private fun WindowContainerTransaction.getClearLaunchAdjacentFlagRootContainer(): IBinder { + return hierarchyOps + // Find the operation with the correct type + .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT } + // For clear flag root operation, toTop is true + .filter { op -> op.toTop } + .map { it.container } + .first() +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java new file mode 100644 index 000000000000..145c8f0ab8af --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java @@ -0,0 +1,123 @@ +/* + * 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.common.split; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; + +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.SystemClock; +import android.provider.DeviceConfig; +import android.view.InputDevice; +import android.view.InsetsState; +import android.view.MotionEvent; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Tests for {@link DividerView} */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DividerViewTest extends ShellTestCase { + private @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks; + private @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler; + private @Mock DisplayController mDisplayController; + private @Mock DisplayImeController mDisplayImeController; + private @Mock ShellTaskOrganizer mTaskOrganizer; + private SplitLayout mSplitLayout; + private DividerView mDividerView; + + @Before + @UiThreadTest + public void setup() { + MockitoAnnotations.initMocks(this); + Configuration configuration = getConfiguration(); + mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration, + mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController, + mTaskOrganizer, SplitLayout.PARALLAX_NONE); + SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager", + mContext, + configuration, mCallbacks); + splitWindowManager.init(mSplitLayout, new InsetsState()); + mDividerView = spy((DividerView) splitWindowManager.getDividerView()); + } + + @Test + @UiThreadTest + public void testHoverDividerView() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED, + "true", false); + + Rect dividerBounds = mSplitLayout.getDividerBounds(); + int x = dividerBounds.centerX(); + int y = dividerBounds.centerY(); + long downTime = SystemClock.uptimeMillis(); + mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_ENTER, x, y)); + + verify(mDividerView, times(1)).setHovering(); + + mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_EXIT, x, y)); + + verify(mDividerView, times(1)).releaseHovering(); + + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED, + "false", false); + } + + private static MotionEvent getMotionEvent(long eventTime, int action, float x, float y) { + MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties(); + properties.id = 0; + properties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN; + + MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); + coords.pressure = 1; + coords.size = 1; + coords.x = x; + coords.y = y; + + return MotionEvent.obtain(eventTime, eventTime, action, 1, + new MotionEvent.PointerProperties[]{properties}, + new MotionEvent.PointerCoords[]{coords}, 0, 0, 1.0f, 1.0f, 0, 0, + InputDevice.SOURCE_TOUCHSCREEN, 0); + } + + private static Configuration getConfiguration() { + final Configuration configuration = new Configuration(); + configuration.unset(); + configuration.orientation = ORIENTATION_LANDSCAPE; + configuration.windowConfiguration.setRotation(0); + configuration.windowConfiguration.setBounds(new Rect(0, 0, 1080, 2160)); + return configuration; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 443cea245a4f..ad84c7fb6128 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -18,9 +18,9 @@ package com.android.wm.shell.common.split; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; - +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS; import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -38,7 +38,6 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.internal.policy.DividerSnapAlgorithm; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; @@ -147,7 +146,7 @@ public class SplitLayoutTests extends ShellTestCase { public void testSnapToDismissStart() { // verify it callbacks properly when the snap target indicates dismissing split. DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */, - DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START); + SNAP_TO_START_AND_DISMISS); mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget); waitDividerFlingFinished(); @@ -159,7 +158,7 @@ public class SplitLayoutTests extends ShellTestCase { public void testSnapToDismissEnd() { // verify it callbacks properly when the snap target indicates dismissing split. DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */, - DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END); + SNAP_TO_END_AND_DISMISS); mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget); waitDividerFlingFinished(); 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 a6501f05475f..9b9600e4a51e 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 @@ -41,6 +41,7 @@ import android.content.res.Configuration; import android.testing.AndroidTestingRunner; import android.view.InsetsSource; import android.view.InsetsState; +import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; @@ -58,6 +59,8 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import dagger.Lazy; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,8 +69,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import dagger.Lazy; - /** * Tests for {@link CompatUIController}. * @@ -82,21 +83,39 @@ public class CompatUIControllerTest extends ShellTestCase { private CompatUIController mController; private ShellInit mShellInit; - private @Mock ShellController mMockShellController; - private @Mock DisplayController mMockDisplayController; - private @Mock DisplayInsetsController mMockDisplayInsetsController; - private @Mock DisplayLayout mMockDisplayLayout; - private @Mock DisplayImeController mMockImeController; - private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener; - private @Mock SyncTransactionQueue mMockSyncQueue; - private @Mock ShellExecutor mMockExecutor; - private @Mock Lazy<Transitions> mMockTransitionsLazy; - private @Mock CompatUIWindowManager mMockCompatLayout; - private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout; - private @Mock RestartDialogWindowManager mMockRestartDialogLayout; - private @Mock DockStateReader mDockStateReader; - private @Mock CompatUIConfiguration mCompatUIConfiguration; - private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler; + @Mock + private ShellController mMockShellController; + @Mock + private DisplayController mMockDisplayController; + @Mock + private DisplayInsetsController mMockDisplayInsetsController; + @Mock + private DisplayLayout mMockDisplayLayout; + @Mock + private DisplayImeController mMockImeController; + @Mock + private ShellTaskOrganizer.TaskListener mMockTaskListener; + @Mock + private SyncTransactionQueue mMockSyncQueue; + @Mock + private ShellExecutor mMockExecutor; + @Mock + private Lazy<Transitions> mMockTransitionsLazy; + @Mock + private CompatUIWindowManager mMockCompatLayout; + @Mock + private LetterboxEduWindowManager mMockLetterboxEduLayout; + @Mock + private RestartDialogWindowManager mMockRestartDialogLayout; + @Mock + private DockStateReader mDockStateReader; + @Mock + private CompatUIConfiguration mCompatUIConfiguration; + @Mock + private CompatUIShellCommandHandler mCompatUIShellCommandHandler; + + @Mock + private AccessibilityManager mAccessibilityManager; @Captor ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor; @@ -124,7 +143,7 @@ public class CompatUIControllerTest extends ShellTestCase { mController = new CompatUIController(mContext, mShellInit, mMockShellController, mMockDisplayController, mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader, - mCompatUIConfiguration, mCompatUIShellCommandHandler) { + mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager) { @Override CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java index 5f294d53b662..3bce2b824e28 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java @@ -44,7 +44,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; +import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import junit.framework.Assert; 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 78c3cbdaace6..4c837e635939 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 @@ -53,7 +53,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; +import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import junit.framework.Assert; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java index 973a99c269ea..5867a8553d53 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java @@ -40,6 +40,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.function.BiConsumer; + /** * Tests for {@link ReachabilityEduWindowManager}. * @@ -57,6 +59,8 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase { private CompatUIConfiguration mCompatUIConfiguration; @Mock private DisplayLayout mDisplayLayout; + @Mock + private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback; private TestShellExecutor mExecutor; private TaskInfo mTaskInfo; private ReachabilityEduWindowManager mWindowManager; @@ -104,6 +108,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase { private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) { return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue, - mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor); + mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor, + mOnDismissCallback, flags -> 0); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java new file mode 100644 index 000000000000..ce1290b38830 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java @@ -0,0 +1,154 @@ +/* + * 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.compatui; + +import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.app.TaskInfo.CameraCompatControlState; +import android.content.ComponentName; +import android.testing.AndroidTestingRunner; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.SurfaceControlViewHost; +import android.widget.ImageButton; +import android.widget.LinearLayout; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.BiConsumer; + +/** + * Tests for {@link UserAspectRatioSettingsLayout}. + * + * Build/Install/Run: + * atest WMShellUnitTests:UserAspectRatioSettingsLayoutTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { + + private static final int TASK_ID = 1; + + @Mock + private SyncTransactionQueue mSyncTransactionQueue; + @Mock + private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> + mOnUserAspectRatioSettingsButtonClicked; + @Mock + private ShellTaskOrganizer.TaskListener mTaskListener; + @Mock + private SurfaceControlViewHost mViewHost; + @Captor + private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor; + @Captor + private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor; + + private UserAspectRatioSettingsWindowManager mWindowManager; + private UserAspectRatioSettingsLayout mLayout; + private TaskInfo mTaskInfo; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, + mSyncTransactionQueue, mTaskListener, new DisplayLayout(), + new CompatUIController.CompatUIHintsState(), + mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor(), flags -> 0); + + mLayout = (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate( + R.layout.user_aspect_ratio_settings_layout, null); + mLayout.inject(mWindowManager); + + spyOn(mWindowManager); + spyOn(mLayout); + doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); + doReturn(mLayout).when(mWindowManager).inflateLayout(); + } + + @Test + public void testOnClickForUserAspectRatioSettingsButton() { + final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button); + button.performClick(); + + verify(mWindowManager).onUserAspectRatioSettingsButtonClicked(); + verify(mOnUserAspectRatioSettingsButtonClicked).accept( + mUserAspectRationTaskInfoCaptor.capture(), + mUserAspectRatioTaskListenerCaptor.capture()); + final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = + new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(), + mUserAspectRatioTaskListenerCaptor.getValue()); + Assert.assertEquals(mTaskInfo, result.first); + Assert.assertEquals(mTaskListener, result.second); + } + + @Test + public void testOnLongClickForUserAspectRatioButton() { + doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked(); + + final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button); + button.performLongClick(); + + verify(mWindowManager).onUserAspectRatioSettingsButtonLongClicked(); + } + + @Test + public void testOnClickForUserAspectRatioSettingsHint() { + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.createLayout(/* canShow= */ true); + final LinearLayout sizeCompatHint = mLayout.findViewById( + R.id.user_aspect_ratio_settings_hint); + sizeCompatHint.performClick(); + + verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ false); + } + + private static TaskInfo createTaskInfo(boolean hasSizeCompat, + @CameraCompatControlState int cameraCompatControlState) { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = TASK_ID; + taskInfo.topActivityInSizeCompat = hasSizeCompat; + taskInfo.cameraCompatControlState = cameraCompatControlState; + taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity"); + return taskInfo; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java new file mode 100644 index 000000000000..08cc2f763135 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -0,0 +1,350 @@ +/* + * 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.compatui; + +import static android.view.WindowInsets.Type.navigationBars; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.content.ComponentName; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.util.Pair; +import android.view.DisplayInfo; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiConsumer; + +/** + * Tests for {@link UserAspectRatioSettingsWindowManager}. + * + * Build/Install/Run: + * atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { + + private static final int TASK_ID = 1; + + @Mock private SyncTransactionQueue mSyncTransactionQueue; + @Mock + private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> + mOnUserAspectRatioSettingsButtonClicked; + @Mock private ShellTaskOrganizer.TaskListener mTaskListener; + @Mock private UserAspectRatioSettingsLayout mLayout; + @Mock private SurfaceControlViewHost mViewHost; + @Captor + private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor; + @Captor + private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor; + + private final Set<String> mPackageNameCache = new HashSet<>(); + + private UserAspectRatioSettingsWindowManager mWindowManager; + private TaskInfo mTaskInfo; + + private TestShellExecutor mExecutor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mExecutor = new TestShellExecutor(); + mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + false, /* topActivityBoundsLetterboxed */ true); + mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, + mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), + mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0); + spyOn(mWindowManager); + doReturn(mLayout).when(mWindowManager).inflateLayout(); + doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); + } + + @Test + public void testCreateUserAspectRatioButton() { + // Doesn't create layout if show is false. + mWindowManager.mHasUserAspectRatioSettingsButton = true; + assertTrue(mWindowManager.createLayout(/* canShow= */ false)); + + verify(mWindowManager, never()).inflateLayout(); + + // Doesn't create hint popup. + mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true; + assertTrue(mWindowManager.createLayout(/* canShow= */ true)); + + verify(mWindowManager).inflateLayout(); + mExecutor.flushAll(); + verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true); + verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true); + + // Creates hint popup. + clearInvocations(mWindowManager); + clearInvocations(mLayout); + mWindowManager.release(); + mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = false; + assertTrue(mWindowManager.createLayout(/* canShow= */ true)); + + verify(mWindowManager).inflateLayout(); + assertNotNull(mLayout); + mExecutor.flushAll(); + verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true); + verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true); + assertTrue(mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint); + + // Returns false and doesn't create layout if mHasUserAspectRatioSettingsButton is false. + clearInvocations(mWindowManager); + mWindowManager.release(); + mWindowManager.mHasUserAspectRatioSettingsButton = false; + assertFalse(mWindowManager.createLayout(/* canShow= */ true)); + + verify(mWindowManager, never()).inflateLayout(); + } + + @Test + public void testRelease() { + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.createLayout(/* canShow= */ true); + + verify(mWindowManager).inflateLayout(); + + mWindowManager.release(); + + verify(mViewHost).release(); + } + + @Test + public void testUpdateCompatInfo() { + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.createLayout(/* canShow= */ true); + + // No diff + clearInvocations(mWindowManager); + TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true); + assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true)); + + verify(mWindowManager, never()).updateSurfacePosition(); + verify(mWindowManager, never()).release(); + verify(mWindowManager, never()).createLayout(anyBoolean()); + + + // Change task listener, recreate button. + clearInvocations(mWindowManager); + final ShellTaskOrganizer.TaskListener newTaskListener = mock( + ShellTaskOrganizer.TaskListener.class); + assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true)); + + verify(mWindowManager).release(); + verify(mWindowManager).createLayout(/* canShow= */ true); + + // Change has eligibleForUserAspectRatioButton to false, dispose the component + clearInvocations(mWindowManager); + clearInvocations(mLayout); + taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + false, /* topActivityBoundsLetterboxed */ true); + assertFalse( + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true)); + verify(mWindowManager).release(); + } + + @Test + public void testUpdateCompatInfoLayoutNotInflatedYet() { + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.createLayout(/* canShow= */ false); + + verify(mWindowManager, never()).inflateLayout(); + + // Change topActivityInSizeCompat to false and pass canShow true, layout shouldn't be + // inflated + clearInvocations(mWindowManager); + TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + false, /* topActivityBoundsLetterboxed */ true); + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + + verify(mWindowManager, never()).inflateLayout(); + + // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated. + clearInvocations(mWindowManager); + taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true); + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + + verify(mWindowManager).inflateLayout(); + } + + @Test + public void testUpdateDisplayLayout() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); + + mWindowManager.updateDisplayLayout(displayLayout1); + verify(mWindowManager).updateSurfacePosition(); + + // No update if the display bounds is the same. + clearInvocations(mWindowManager); + final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); + mWindowManager.updateDisplayLayout(displayLayout2); + verify(mWindowManager, never()).updateSurfacePosition(); + } + + @Test + public void testUpdateDisplayLayoutInsets() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + final DisplayLayout displayLayout = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false); + + mWindowManager.updateDisplayLayout(displayLayout); + verify(mWindowManager).updateSurfacePosition(); + + // Update if the insets change on the existing display layout + clearInvocations(mWindowManager); + InsetsState insetsState = new InsetsState(); + insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000)); + InsetsSource insetsSource = new InsetsSource( + InsetsSource.createId(null, 0, navigationBars()), navigationBars()); + insetsSource.setFrame(0, 1800, 1000, 2000); + insetsState.addSource(insetsSource); + displayLayout.setInsets(mContext.getResources(), insetsState); + mWindowManager.updateDisplayLayout(displayLayout); + verify(mWindowManager).updateSurfacePosition(); + } + + @Test + public void testUpdateVisibility() { + // Create button if it is not created. + mWindowManager.removeLayout(); + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.updateVisibility(/* canShow= */ true); + + verify(mWindowManager).createLayout(/* canShow= */ true); + + // Hide button. + clearInvocations(mWindowManager); + doReturn(View.VISIBLE).when(mLayout).getVisibility(); + mWindowManager.updateVisibility(/* canShow= */ false); + + verify(mWindowManager, never()).createLayout(anyBoolean()); + verify(mLayout).setVisibility(View.GONE); + + // Show button. + doReturn(View.GONE).when(mLayout).getVisibility(); + mWindowManager.updateVisibility(/* canShow= */ true); + + verify(mWindowManager, never()).createLayout(anyBoolean()); + verify(mLayout).setVisibility(View.VISIBLE); + } + + @Test + public void testAttachToParentSurface() { + final SurfaceControl.Builder b = new SurfaceControl.Builder(); + mWindowManager.attachToParentSurface(b); + + verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b); + } + + @Test + public void testOnUserAspectRatioButtonClicked() { + mWindowManager.onUserAspectRatioSettingsButtonClicked(); + + verify(mOnUserAspectRatioSettingsButtonClicked).accept( + mUserAspectRationTaskInfoCaptor.capture(), + mUserAspectRatioTaskListenerCaptor.capture()); + final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = + new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(), + mUserAspectRatioTaskListenerCaptor.getValue()); + Assert.assertEquals(mTaskInfo, result.first); + Assert.assertEquals(mTaskListener, result.second); + } + + @Test + public void testOnUserAspectRatioButtonLongClicked_showHint() { + // Not create hint popup. + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true; + mWindowManager.createLayout(/* canShow= */ true); + + verify(mWindowManager).inflateLayout(); + verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true); + + mWindowManager.onUserAspectRatioSettingsButtonLongClicked(); + + verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true); + } + + @Test + public void testWhenDockedStateHasChanged_needsToBeRecreated() { + ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); + newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK; + + Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener)); + } + + private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton, + boolean topActivityBoundsLetterboxed) { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = TASK_ID; + taskInfo.topActivityEligibleForUserAspectRatioButton = eligibleForUserAspectRatioButton; + taskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed; + taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK; + taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity"); + return taskInfo; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java deleted file mode 100644 index d6387ee5ae13..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ /dev/null @@ -1,530 +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.desktopmode; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_NONE; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_FRONT; -import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask; -import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask; -import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createHomeTask; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager.RunningTaskInfo; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.testing.AndroidTestingRunner; -import android.window.DisplayAreaInfo; -import android.window.TransitionRequestInfo; -import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransaction.Change; -import android.window.WindowContainerTransaction.HierarchyOp; - -import androidx.test.filters.SmallTest; - -import com.android.dx.mockito.inline.extended.StaticMockitoSession; -import com.android.wm.shell.RootTaskDisplayAreaOrganizer; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.TestShellExecutor; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.sysui.ShellController; -import com.android.wm.shell.sysui.ShellInit; -import com.android.wm.shell.transition.Transitions; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.Arrays; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class DesktopModeControllerTest extends ShellTestCase { - - private static final int SECOND_DISPLAY = 2; - - @Mock - private ShellController mShellController; - @Mock - private ShellTaskOrganizer mShellTaskOrganizer; - @Mock - private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; - @Mock - private ShellExecutor mTestExecutor; - @Mock - private Handler mMockHandler; - @Mock - private Transitions mTransitions; - private DesktopModeController mController; - private DesktopModeTaskRepository mDesktopModeTaskRepository; - private ShellInit mShellInit; - private StaticMockitoSession mMockitoSession; - - @Before - public void setUp() { - mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking(); - when(DesktopModeStatus.isProto1Enabled()).thenReturn(true); - when(DesktopModeStatus.isActive(any())).thenReturn(true); - - mShellInit = Mockito.spy(new ShellInit(mTestExecutor)); - - mDesktopModeTaskRepository = new DesktopModeTaskRepository(); - - mController = createController(); - - when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>()); - - mShellInit.init(); - clearInvocations(mShellTaskOrganizer); - clearInvocations(mRootTaskDisplayAreaOrganizer); - clearInvocations(mTransitions); - } - - @After - public void tearDown() { - mMockitoSession.finishMocking(); - } - - @Test - public void instantiate_addInitCallback() { - verify(mShellInit).addInitCallback(any(), any()); - } - - @Test - public void instantiate_flagOff_doNotAddInitCallback() { - when(DesktopModeStatus.isProto1Enabled()).thenReturn(false); - clearInvocations(mShellInit); - - createController(); - - verify(mShellInit, never()).addInitCallback(any(), any()); - } - - @Test - public void testDesktopModeEnabled_rootTdaSetToFreeform() { - DisplayAreaInfo displayAreaInfo = createMockDisplayArea(); - - mController.updateDesktopModeActive(true); - WindowContainerTransaction wct = getDesktopModeSwitchTransaction(); - - // 1 change: Root TDA windowing mode - assertThat(wct.getChanges().size()).isEqualTo(1); - // Verify WCT has a change for setting windowing mode to freeform - Change change = wct.getChanges().get(displayAreaInfo.token.asBinder()); - assertThat(change).isNotNull(); - assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM); - } - - @Test - public void testDesktopModeDisabled_rootTdaSetToFullscreen() { - DisplayAreaInfo displayAreaInfo = createMockDisplayArea(); - - mController.updateDesktopModeActive(false); - WindowContainerTransaction wct = getDesktopModeSwitchTransaction(); - - // 1 change: Root TDA windowing mode - assertThat(wct.getChanges().size()).isEqualTo(1); - // Verify WCT has a change for setting windowing mode to fullscreen - Change change = wct.getChanges().get(displayAreaInfo.token.asBinder()); - assertThat(change).isNotNull(); - assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN); - } - - @Test - public void testDesktopModeEnabled_windowingModeCleared() { - createMockDisplayArea(); - RunningTaskInfo freeformTask = createFreeformTask(); - RunningTaskInfo fullscreenTask = createFullscreenTask(); - RunningTaskInfo homeTask = createHomeTask(); - when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>( - Arrays.asList(freeformTask, fullscreenTask, homeTask))); - - mController.updateDesktopModeActive(true); - WindowContainerTransaction wct = getDesktopModeSwitchTransaction(); - - // 2 changes: Root TDA windowing mode and 1 task - assertThat(wct.getChanges().size()).isEqualTo(2); - // No changes for tasks that are not standard or freeform - assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull(); - assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull(); - // Standard freeform task has windowing mode cleared - Change change = wct.getChanges().get(freeformTask.token.asBinder()); - assertThat(change).isNotNull(); - assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED); - } - - @Test - public void testDesktopModeDisabled_windowingModeAndBoundsCleared() { - createMockDisplayArea(); - RunningTaskInfo freeformTask = createFreeformTask(); - RunningTaskInfo fullscreenTask = createFullscreenTask(); - RunningTaskInfo homeTask = createHomeTask(); - when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>( - Arrays.asList(freeformTask, fullscreenTask, homeTask))); - - mController.updateDesktopModeActive(false); - WindowContainerTransaction wct = getDesktopModeSwitchTransaction(); - - // 3 changes: Root TDA windowing mode and 2 tasks - assertThat(wct.getChanges().size()).isEqualTo(3); - // No changes to home task - assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull(); - // Standard tasks have bounds cleared - assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder())); - assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder())); - // Freeform standard tasks have windowing mode cleared - assertThat(wct.getChanges().get( - freeformTask.token.asBinder()).getWindowingMode()).isEqualTo( - WINDOWING_MODE_UNDEFINED); - } - - @Test - public void testDesktopModeEnabled_homeTaskBehindVisibleTask() { - createMockDisplayArea(); - RunningTaskInfo fullscreenTask1 = createFullscreenTask(); - fullscreenTask1.isVisible = true; - RunningTaskInfo fullscreenTask2 = createFullscreenTask(); - fullscreenTask2.isVisible = false; - RunningTaskInfo homeTask = createHomeTask(); - when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>( - Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask))); - - mController.updateDesktopModeActive(true); - WindowContainerTransaction wct = getDesktopModeSwitchTransaction(); - - // Check that there are hierarchy changes for home task and visible task - assertThat(wct.getHierarchyOps()).hasSize(2); - // First show home task - HierarchyOp op1 = wct.getHierarchyOps().get(0); - assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); - assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder()); - - // Then visible task on top of it - HierarchyOp op2 = wct.getHierarchyOps().get(1); - assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); - assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder()); - } - - @Test - public void testShowDesktopApps_allAppsInvisible_bringsToFront() { - // Set up two active tasks on desktop, task2 is on top of task1. - RunningTaskInfo freeformTask1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask1.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks( - DEFAULT_DISPLAY, freeformTask1.taskId, false /* visible */); - RunningTaskInfo freeformTask2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask2.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks( - DEFAULT_DISPLAY, freeformTask2.taskId, false /* visible */); - when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn( - freeformTask1); - when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn( - freeformTask2); - - // Run show desktop apps logic - mController.showDesktopApps(DEFAULT_DISPLAY); - - final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); - // Check wct has reorder calls - assertThat(wct.getHierarchyOps()).hasSize(2); - - // Task 1 appeared first, must be first reorder to top. - HierarchyOp op1 = wct.getHierarchyOps().get(0); - assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); - assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder()); - - // Task 2 appeared last, must be last reorder to top. - HierarchyOp op2 = wct.getHierarchyOps().get(1); - assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); - assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder()); - } - - @Test - public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() { - final RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, - true /* visible */); - when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1); - final RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, - true /* visible */); - when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2); - - mController.showDesktopApps(DEFAULT_DISPLAY); - - final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); - // Check wct has reorder calls - assertThat(wct.getHierarchyOps()).hasSize(2); - // Task 1 appeared first, must be first reorder to top. - HierarchyOp op1 = wct.getHierarchyOps().get(0); - assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); - assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder()); - - // Task 2 appeared last, must be last reorder to top. - HierarchyOp op2 = wct.getHierarchyOps().get(1); - assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); - assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder()); - } - - @Test - public void testShowDesktopApps_someAppsInvisible_reordersAll() { - final RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, - false /* visible */); - when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1); - final RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, - true /* visible */); - when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2); - - mController.showDesktopApps(DEFAULT_DISPLAY); - - final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); - // Both tasks should be reordered to top, even if one was already visible. - assertThat(wct.getHierarchyOps()).hasSize(2); - final HierarchyOp op1 = wct.getHierarchyOps().get(0); - assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); - assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder()); - final HierarchyOp op2 = wct.getHierarchyOps().get(1); - assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); - assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder()); - } - - @Test - public void testShowDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() { - RunningTaskInfo taskDefaultDisplay = createFreeformTask(DEFAULT_DISPLAY); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks( - DEFAULT_DISPLAY, taskDefaultDisplay.taskId, false /* visible */); - when(mShellTaskOrganizer.getRunningTaskInfo(taskDefaultDisplay.taskId)).thenReturn( - taskDefaultDisplay); - - RunningTaskInfo taskSecondDisplay = createFreeformTask(SECOND_DISPLAY); - mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks( - SECOND_DISPLAY, taskSecondDisplay.taskId, false /* visible */); - when(mShellTaskOrganizer.getRunningTaskInfo(taskSecondDisplay.taskId)).thenReturn( - taskSecondDisplay); - - mController.showDesktopApps(DEFAULT_DISPLAY); - - WindowContainerTransaction wct = getBringAppsToFrontTransaction(); - assertThat(wct.getHierarchyOps()).hasSize(1); - HierarchyOp op = wct.getHierarchyOps().get(0); - assertThat(op.getContainer()).isEqualTo(taskDefaultDisplay.token.asBinder()); - } - - @Test - public void testGetVisibleTaskCount_noTasks_returnsZero() { - assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0); - } - - @Test - public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() { - RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, - true /* visible */); - - RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, - true /* visible */); - - assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2); - } - - @Test - public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() { - RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, - true /* visible */); - - RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, - false /* visible */); - - assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1); - } - - @Test - public void testGetVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() { - RunningTaskInfo taskDefaultDisplay = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, - taskDefaultDisplay.taskId, - true /* visible */); - - RunningTaskInfo taskSecondDisplay = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId); - mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(SECOND_DISPLAY, - taskSecondDisplay.taskId, - true /* visible */); - - assertThat(mController.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1); - } - - @Test - public void testHandleTransitionRequest_desktopModeNotActive_returnsNull() { - when(DesktopModeStatus.isActive(any())).thenReturn(false); - WindowContainerTransaction wct = mController.handleRequest( - new Binder(), - new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */)); - assertThat(wct).isNull(); - } - - @Test - public void testHandleTransitionRequest_unsupportedTransit_returnsNull() { - WindowContainerTransaction wct = mController.handleRequest( - new Binder(), - new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */)); - assertThat(wct).isNull(); - } - - @Test - public void testHandleTransitionRequest_notFreeform_returnsNull() { - RunningTaskInfo trigger = new RunningTaskInfo(); - trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - WindowContainerTransaction wct = mController.handleRequest( - new Binder(), - new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */)); - assertThat(wct).isNull(); - } - - @Test - public void testHandleTransitionRequest_taskOpen_returnsWct() { - RunningTaskInfo trigger = new RunningTaskInfo(); - trigger.token = new MockToken().token(); - trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - WindowContainerTransaction wct = mController.handleRequest( - mock(IBinder.class), - new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */)); - assertThat(wct).isNotNull(); - } - - @Test - public void testHandleTransitionRequest_taskToFront_returnsWct() { - RunningTaskInfo trigger = new RunningTaskInfo(); - trigger.token = new MockToken().token(); - trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - WindowContainerTransaction wct = mController.handleRequest( - mock(IBinder.class), - new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */)); - assertThat(wct).isNotNull(); - } - - @Test - public void testHandleTransitionRequest_taskOpen_doesNotStartAnotherTransition() { - RunningTaskInfo trigger = new RunningTaskInfo(); - trigger.token = new MockToken().token(); - trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - mController.handleRequest( - mock(IBinder.class), - new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */)); - verifyZeroInteractions(mTransitions); - } - - private DesktopModeController createController() { - return new DesktopModeController(mContext, mShellInit, mShellController, - mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions, - mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor()); - } - - private DisplayAreaInfo createMockDisplayArea() { - DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().token(), - mContext.getDisplayId(), 0); - when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId())) - .thenReturn(displayAreaInfo); - return displayAreaInfo; - } - - private WindowContainerTransaction getDesktopModeSwitchTransaction() { - ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass( - WindowContainerTransaction.class); - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any()); - } else { - verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture()); - } - return arg.getValue(); - } - - private WindowContainerTransaction getBringAppsToFrontTransaction() { - final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass( - WindowContainerTransaction.class); - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - verify(mTransitions).startTransition(eq(TRANSIT_NONE), arg.capture(), any()); - } else { - verify(mShellTaskOrganizer).applyTransaction(arg.capture()); - } - return arg.getValue(); - } - - private void assertThatBoundsCleared(Change change) { - assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue(); - assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue(); - } - -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index 3bc2f0e8674e..3fe78efdf2b1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -129,6 +129,18 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test + fun addListener_notifiesStashed() { + repo.setStashed(DEFAULT_DISPLAY, true) + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + executor.flushAll() + + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test fun addListener_tasksOnDifferentDisplay_doesNotNotify() { repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true) val listener = TestVisibilityListener() @@ -313,6 +325,65 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { assertThat(tasks.first()).isEqualTo(6) } + @Test + fun setStashed_stateIsUpdatedForTheDisplay() { + repo.setStashed(DEFAULT_DISPLAY, true) + assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue() + assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse() + + repo.setStashed(DEFAULT_DISPLAY, false) + assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse() + } + + @Test + fun setStashed_notifyListener() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + repo.setStashed(DEFAULT_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) + + repo.setStashed(DEFAULT_DISPLAY, false) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isFalse() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2) + } + + @Test + fun setStashed_secondCallDoesNotNotify() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + repo.setStashed(DEFAULT_DISPLAY, true) + repo.setStashed(DEFAULT_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test + fun setStashed_tracksPerDisplay() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + + repo.setStashed(DEFAULT_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedOnSecondaryDisplay).isFalse() + + repo.setStashed(SECOND_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedOnSecondaryDisplay).isTrue() + + repo.setStashed(DEFAULT_DISPLAY, false) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isFalse() + assertThat(listener.stashedOnSecondaryDisplay).isTrue() + } + class TestListener : DesktopModeTaskRepository.ActiveTasksListener { var activeChangesOnDefaultDisplay = 0 var activeChangesOnSecondaryDisplay = 0 @@ -332,6 +403,12 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { var visibleChangesOnDefaultDisplay = 0 var visibleChangesOnSecondaryDisplay = 0 + var stashedOnDefaultDisplay = false + var stashedOnSecondaryDisplay = false + + var stashedChangesOnDefaultDisplay = 0 + var stashedChangesOnSecondaryDisplay = 0 + override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) { when (displayId) { DEFAULT_DISPLAY -> { @@ -345,6 +422,20 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { else -> fail("Visible task listener received unexpected display id: $displayId") } } + + override fun onStashedChanged(displayId: Int, stashed: Boolean) { + when (displayId) { + DEFAULT_DISPLAY -> { + stashedOnDefaultDisplay = stashed + stashedChangesOnDefaultDisplay++ + } + SECOND_DISPLAY -> { + stashedOnSecondaryDisplay = stashed + stashedChangesOnDefaultDisplay++ + } + else -> fail("Visible task listener received unexpected display id: $displayId") + } + } } companion object { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 1335ebf105a6..c6cccc059e89 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -39,21 +39,27 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.LaunchAdjacentController import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask +import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask +import com.android.wm.shell.splitscreen.SplitScreenController +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 com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.After @@ -77,6 +83,7 @@ import org.mockito.Mockito.`when` as whenever class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor + @Mock lateinit var shellCommandHandler: ShellCommandHandler @Mock lateinit var shellController: ShellController @Mock lateinit var displayController: DisplayController @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer @@ -85,19 +92,25 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var transitions: Transitions @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler + @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler: + ToggleResizeDesktopTaskTransitionHandler + @Mock lateinit var launchAdjacentController: LaunchAdjacentController + @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration + @Mock lateinit var splitScreenController: SplitScreenController private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController private lateinit var shellInit: ShellInit private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository + private val shellExecutor = TestShellExecutor() // Mock running tasks are registered here so we can get the list from mock shell task organizer private val runningTasks = mutableListOf<RunningTaskInfo>() @Before fun setUp() { mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking() - whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(true) + whenever(DesktopModeStatus.isEnabled()).thenReturn(true) shellInit = Mockito.spy(ShellInit(testExecutor)) desktopModeTaskRepository = DesktopModeTaskRepository() @@ -106,6 +119,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } controller = createController() + controller.splitScreenController = splitScreenController shellInit.init() } @@ -114,6 +128,7 @@ class DesktopTasksControllerTest : ShellTestCase() { return DesktopTasksController( context, shellInit, + shellCommandHandler, shellController, displayController, shellTaskOrganizer, @@ -122,8 +137,10 @@ class DesktopTasksControllerTest : ShellTestCase() { transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, + mToggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository, - TestShellExecutor() + launchAdjacentController, + shellExecutor ) } @@ -141,7 +158,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun instantiate_flagOff_doNotAddInitCallback() { - whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(false) + whenever(DesktopModeStatus.isEnabled()).thenReturn(false) clearInvocations(shellInit) createController() @@ -262,17 +279,28 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop() { + fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() - controller.moveToDesktop(task) - val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN + controller.moveToDesktop(desktopModeWindowDecoration, task) + val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) } @Test + fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() { + val task = setUpFullscreenTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM + controller.moveToDesktop(desktopModeWindowDecoration, task) + val wct = getLatestMoveToDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test fun moveToDesktop_nonExistentTask_doesNothing() { - controller.moveToDesktop(999) + controller.moveToDesktop(desktopModeWindowDecoration, 999) verifyWCTNotExecuted() } @@ -283,9 +311,9 @@ class DesktopTasksControllerTest : ShellTestCase() { val fullscreenTask = setUpFullscreenTask() markTaskHidden(freeformTask) - controller.moveToDesktop(fullscreenTask) + controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask) - with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + with(getLatestMoveToDesktopWct()) { // Operations should include home task, freeform task assertThat(hierarchyOps).hasSize(3) assertReorderSequence(homeTask, freeformTask, fullscreenTask) @@ -305,9 +333,9 @@ class DesktopTasksControllerTest : ShellTestCase() { val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY) markTaskHidden(freeformTaskSecond) - controller.moveToDesktop(fullscreenTaskDefault) + controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault) - with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + with(getLatestMoveToDesktopWct()) { // Check that hierarchy operations do not include tasks from second display assertThat(hierarchyOps.map { it.container }) .doesNotContain(homeTaskSecond.token.asBinder()) @@ -317,12 +345,47 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToFullscreen() { + fun moveToDesktop_splitTaskExitsSplit() { + var task = setUpSplitScreenTask() + controller.moveToDesktop(desktopModeWindowDecoration, task) + val wct = getLatestMoveToDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(), + eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP) + ) + } + + @Test + fun moveToDesktop_fullscreenTaskDoesNotExitSplit() { + var task = setUpFullscreenTask() + controller.moveToDesktop(desktopModeWindowDecoration, task) + val wct = getLatestMoveToDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + verify(splitScreenController, never()).prepareExitSplitScreen(any(), anyInt(), + eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP) + ) + } + + @Test + fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() { val task = setUpFreeformTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN controller.moveToFullscreen(task) val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) assertThat(wct.changes[task.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test + fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() { + val task = setUpFreeformTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM + controller.moveToFullscreen(task) + val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) } @Test @@ -345,6 +408,18 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveTaskToFront_postsWctWithReorderOp() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + controller.moveTaskToFront(task1) + + val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT) + assertThat(wct.hierarchyOps).hasSize(1) + wct.assertReorderAt(index = 0, task1) + } + + @Test fun moveToNextDisplay_noOtherDisplays() { whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY)) val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) @@ -451,6 +526,28 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) + + val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) + markTaskHidden(stashedFreeformTask) + + val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY) + + controller.stashDesktopApps(DEFAULT_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + assertThat(result).isNotNull() + result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask) + assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + + // Stashed state should be cleared + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() + } + + @Test fun handleRequest_freeformTask_freeformVisible_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -501,6 +598,26 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) + + val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) + markTaskHidden(stashedFreeformTask) + + val freeformTask = createFreeformTask(DEFAULT_DISPLAY) + + controller.stashDesktopApps(DEFAULT_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(freeformTask)) + assertThat(result).isNotNull() + result?.assertReorderSequence(stashedFreeformTask, freeformTask) + + // Stashed state should be cleared + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() + } + + @Test fun handleRequest_notOpenOrToFrontTransition_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -539,6 +656,50 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() } + @Test + fun stashDesktopApps_stateUpdates() { + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) + + controller.stashDesktopApps(DEFAULT_DISPLAY) + + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue() + assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse() + } + + @Test + fun hideStashedDesktopApps_stateUpdates() { + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) + + desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true) + desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true) + controller.hideStashedDesktopApps(DEFAULT_DISPLAY) + + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() + // Check that second display is not affected + assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue() + } + + @Test + fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() { + val task = setUpFreeformTask() + clearInvocations(launchAdjacentController) + + markTaskVisible(task) + shellExecutor.flushAll() + verify(launchAdjacentController).launchAdjacentEnabled = false + } + + @Test + fun desktopTasksVisibilityChange_invisible_setLaunchAdjacentEnabled() { + val task = setUpFreeformTask() + markTaskVisible(task) + clearInvocations(launchAdjacentController) + + markTaskHidden(task) + shellExecutor.flushAll() + verify(launchAdjacentController).launchAdjacentEnabled = true + } + private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createFreeformTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) @@ -562,6 +723,13 @@ class DesktopTasksControllerTest : ShellTestCase() { return task } + private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + val task = createSplitScreenTask(displayId) + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + runningTasks.add(task) + return task + } + private fun markTaskVisible(task: RunningTaskInfo) { desktopModeTaskRepository.updateVisibleFreeformTasks( task.displayId, @@ -590,6 +758,16 @@ class DesktopTasksControllerTest : ShellTestCase() { return arg.value } + private fun getLatestMoveToDesktopWct(): WindowContainerTransaction { + val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + if (ENABLE_SHELL_TRANSITIONS) { + verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any()) + } else { + verify(shellTaskOrganizer).applyTransaction(arg.capture()) + } + return arg.value + } + private fun verifyWCTNotExecuted() { if (ENABLE_SHELL_TRANSITIONS) { verify(transitions, never()).startTransition(anyInt(), any(), isNull()) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt index cf1ff3214d87..2f6f3207137d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt @@ -21,7 +21,9 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.view.Display.DEFAULT_DISPLAY +import com.android.wm.shell.MockToken import com.android.wm.shell.TestRunningTaskInfoBuilder class DesktopTestHelpers { @@ -44,12 +46,25 @@ class DesktopTestHelpers { @JvmOverloads fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return TestRunningTaskInfoBuilder() - .setDisplayId(displayId) - .setToken(MockToken().token()) - .setActivityType(ACTIVITY_TYPE_STANDARD) - .setWindowingMode(WINDOWING_MODE_FULLSCREEN) - .setLastActiveTime(100) - .build() + .setDisplayId(displayId) + .setToken(MockToken().token()) + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .setLastActiveTime(100) + .build() + } + + /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */ + @JvmStatic + @JvmOverloads + fun createSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + return TestRunningTaskInfoBuilder() + .setDisplayId(displayId) + .setToken(MockToken().token()) + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) + .setLastActiveTime(100) + .build() } /** Create a new home task */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java index 8592dea19289..772d97d8eb32 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.WindowConfiguration; +import android.graphics.PointF; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; @@ -41,6 +42,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator; import junit.framework.AssertionFailedError; @@ -73,6 +75,10 @@ public class EnterDesktopTaskTransitionHandlerTest { ShellExecutor mExecutor; @Mock SurfaceControl mSurfaceControl; + @Mock + MoveToDesktopAnimator mMoveToDesktopAnimator; + @Mock + PointF mPosition; private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler; @@ -82,6 +88,7 @@ public class EnterDesktopTaskTransitionHandlerTest { doReturn(mExecutor).when(mTransitions).getMainExecutor(); doReturn(mAnimationT).when(mTransactionFactory).get(); + doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition(); mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions, mTransactionFactory); @@ -89,16 +96,20 @@ public class EnterDesktopTaskTransitionHandlerTest { @Test public void testEnterFreeformAnimation() { - final int transitionType = Transitions.TRANSIT_ENTER_FREEFORM; final int taskId = 1; WindowContainerTransaction wct = new WindowContainerTransaction(); doReturn(mToken).when(mTransitions) - .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler); - mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null); + .startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct, + mEnterDesktopTaskTransitionHandler); + doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId(); + + mEnterDesktopTaskTransitionHandler.startMoveToDesktop(wct, + mMoveToDesktopAnimator, null); TransitionInfo.Change change = createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM); - TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_FREEFORM, change); + TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, + change); assertTrue(mEnterDesktopTaskTransitionHandler @@ -110,17 +121,18 @@ public class EnterDesktopTaskTransitionHandlerTest { @Test public void testTransitEnterDesktopModeAnimation() throws Throwable { - final int transitionType = Transitions.TRANSIT_ENTER_DESKTOP_MODE; + final int transitionType = Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE; final int taskId = 1; WindowContainerTransaction wct = new WindowContainerTransaction(); doReturn(mToken).when(mTransitions) .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler); - mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null); + mEnterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, null); TransitionInfo.Change change = createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM); change.setEndAbsBounds(new Rect(0, 0, 1, 1)); - TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_DESKTOP_MODE, change); + TransitionInfo info = createTransitionInfo( + Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, change); runOnUiThread(() -> { try { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index 7c1da35888b8..527dc0149716 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -133,8 +133,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false); mInsets = Insets.of(0, 0, 0, 0); - mPolicy = spy(new DragAndDropPolicy( - mContext, mActivityTaskManager, mSplitScreenStarter, mSplitScreenStarter)); + mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter)); mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY); mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY); setClipDataResizeable(mNonResizeableActivityClipData, false); @@ -206,7 +205,10 @@ public class DragAndDropPolicyTest extends ShellTestCase { @Test public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() { setRunningTask(mHomeTask); - mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); + DragSession dragSession = new DragSession(mContext, mActivityTaskManager, + mLandscapeDisplayLayout, mActivityClipData); + dragSession.update(); + mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); @@ -218,7 +220,10 @@ public class DragAndDropPolicyTest extends ShellTestCase { @Test public void testDragAppOverFullscreenApp_expectSplitScreenTargets() { setRunningTask(mFullscreenAppTask); - mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); + DragSession dragSession = new DragSession(mContext, mActivityTaskManager, + mLandscapeDisplayLayout, mActivityClipData); + dragSession.update(); + mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); @@ -235,7 +240,10 @@ public class DragAndDropPolicyTest extends ShellTestCase { @Test public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() { setRunningTask(mFullscreenAppTask); - mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId); + DragSession dragSession = new DragSession(mContext, mActivityTaskManager, + mPortraitDisplayLayout, mActivityClipData); + dragSession.update(); + mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); @@ -252,7 +260,10 @@ public class DragAndDropPolicyTest extends ShellTestCase { @Test public void testTargetHitRects() { setRunningTask(mFullscreenAppTask); - mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); + DragSession dragSession = new DragSession(mContext, mActivityTaskManager, + mLandscapeDisplayLayout, mActivityClipData); + dragSession.update(); + mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = mPolicy.getTargets(mInsets); for (Target t : targets) { assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java index addc2338144f..46259a8b177f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java @@ -32,7 +32,13 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.SizeSpecSource; import org.junit.Before; import org.junit.Test; @@ -60,7 +66,8 @@ public class PipBoundsAlgorithmTest extends ShellTestCase { private PipBoundsAlgorithm mPipBoundsAlgorithm; private DisplayInfo mDefaultDisplayInfo; - private PipBoundsState mPipBoundsState; private PipSizeSpecHandler mPipSizeSpecHandler; + private PipBoundsState mPipBoundsState; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; @@ -68,11 +75,12 @@ public class PipBoundsAlgorithmTest extends ShellTestCase { public void setUp() throws Exception { initializeMockResources(); mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); - mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState); + + mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); + mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}, - mPipSizeSpecHandler); + mPipDisplayLayoutState, mSizeSpecSource); DisplayLayout layout = new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true); @@ -132,7 +140,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase { @Test public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() { - final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO); + final Size defaultSize = mSizeSpecSource.getDefaultSize(DEFAULT_ASPECT_RATIO); mPipBoundsState.setOverrideMinSize(null); final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java index f32000445ca9..d34e27b57071 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java @@ -35,7 +35,10 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.function.TriConsumer; import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.SizeSpecSource; import org.junit.Before; import org.junit.Test; @@ -58,6 +61,7 @@ public class PipBoundsStateTest extends ShellTestCase { private static final int OVERRIDABLE_MIN_SIZE = 40; private PipBoundsState mPipBoundsState; + private SizeSpecSource mSizeSpecSource; private ComponentName mTestComponentName1; private ComponentName mTestComponentName2; @@ -69,8 +73,8 @@ public class PipBoundsStateTest extends ShellTestCase { OVERRIDABLE_MIN_SIZE); PipDisplayLayoutState pipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipBoundsState = new PipBoundsState(mContext, - new PipSizeSpecHandler(mContext, pipDisplayLayoutState), pipDisplayLayoutState); + mSizeSpecSource = new PhoneSizeSpecSource(mContext, pipDisplayLayoutState); + mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, pipDisplayLayoutState); mTestComponentName1 = new ComponentName(mContext, "component1"); mTestComponentName2 = new ComponentName(mContext, "component2"); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java index b9226d2b9b91..ac13d7ffcd61 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java @@ -25,6 +25,8 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import org.junit.Before; import org.junit.Test; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 842c699fa42d..4e2b7f6d16b2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -52,8 +52,15 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.phone.PhonePipMenuController; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; @@ -87,7 +94,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { private PipBoundsState mPipBoundsState; private PipTransitionState mPipTransitionState; private PipBoundsAlgorithm mPipBoundsAlgorithm; - private PipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; private ComponentName mComponent1; @@ -99,12 +106,12 @@ public class PipTaskOrganizerTest extends ShellTestCase { mComponent1 = new ComponentName(mContext, "component1"); mComponent2 = new ComponentName(mContext, "component2"); mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); - mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState); + mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); + mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); mPipTransitionState = new PipTransitionState(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}, - mPipSizeSpecHandler); + mPipDisplayLayoutState, mSizeSpecSource); mMainExecutor = new TestShellExecutor(); mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java index cc9e26b2c4f1..8c7b47ea7d84 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java @@ -29,8 +29,9 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; import org.junit.Before; import org.junit.Test; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java index 1379aedc2284..3d5cd6939d1b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java @@ -32,7 +32,9 @@ import android.view.DisplayInfo; import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.SizeSpecSource; import org.junit.After; import org.junit.Assert; @@ -47,10 +49,10 @@ import java.util.Map; import java.util.function.Function; /** - * Unit test against {@link PipSizeSpecHandler} with feature flag on. + * Unit test against {@link PhoneSizeSpecSource} */ @RunWith(AndroidTestingRunner.class) -public class PipSizeSpecHandlerTest extends ShellTestCase { +public class PhoneSizeSpecSourceTest extends ShellTestCase { /** A sample overridden min edge size. */ private static final int OVERRIDE_MIN_EDGE_SIZE = 40; /** A sample default min edge size */ @@ -75,7 +77,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { @Mock private Resources mResources; private PipDisplayLayoutState mPipDisplayLayoutState; - private TestPipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; /** * Sets up static Mockito session for SystemProperties and mocks necessary static methods. @@ -110,17 +112,17 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { sExpectedDefaultSizes.put(16f / 9, new Size(600, 338)); sExpectedMinSizes.put(16f / 9, new Size(501, 282)); - sExpectedMaxSizes.put(4f / 3, new Size(892, 669)); - sExpectedDefaultSizes.put(4f / 3, new Size(535, 401)); + sExpectedMaxSizes.put(4f / 3, new Size(893, 670)); + sExpectedDefaultSizes.put(4f / 3, new Size(536, 402)); sExpectedMinSizes.put(4f / 3, new Size(447, 335)); - sExpectedMaxSizes.put(3f / 4, new Size(669, 892)); - sExpectedDefaultSizes.put(3f / 4, new Size(401, 535)); + sExpectedMaxSizes.put(3f / 4, new Size(670, 893)); + sExpectedDefaultSizes.put(3f / 4, new Size(402, 536)); sExpectedMinSizes.put(3f / 4, new Size(335, 447)); - sExpectedMaxSizes.put(9f / 16, new Size(562, 999)); - sExpectedDefaultSizes.put(9f / 16, new Size(337, 599)); - sExpectedMinSizes.put(9f / 16, new Size(281, 500)); + sExpectedMaxSizes.put(9f / 16, new Size(563, 1001)); + sExpectedDefaultSizes.put(9f / 16, new Size(338, 601)); + sExpectedMinSizes.put(9f / 16, new Size(282, 501)); } private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes, @@ -158,10 +160,10 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { mPipDisplayLayoutState.setDisplayLayout(displayLayout); setUpStaticSystemPropertiesSession(); - mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState); + mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); // no overridden min edge size by default - mPipSizeSpecHandler.setOverrideMinSize(null); + mSizeSpecSource.setOverrideMinSize(null); } @After @@ -172,19 +174,19 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { @Test public void testGetMaxSize() { forEveryTestCaseCheck(sExpectedMaxSizes, - (aspectRatio) -> mPipSizeSpecHandler.getMaxSize(aspectRatio)); + (aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio)); } @Test public void testGetDefaultSize() { forEveryTestCaseCheck(sExpectedDefaultSizes, - (aspectRatio) -> mPipSizeSpecHandler.getDefaultSize(aspectRatio)); + (aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio)); } @Test public void testGetMinSize() { forEveryTestCaseCheck(sExpectedMinSizes, - (aspectRatio) -> mPipSizeSpecHandler.getMinSize(aspectRatio)); + (aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio)); } @Test @@ -192,8 +194,8 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { // an initial size with 16:9 aspect ratio Size initSize = new Size(600, 337); - Size expectedSize = new Size(337, 599); - Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16); + Size expectedSize = new Size(338, 601); + Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16); Assert.assertEquals(expectedSize, actualSize); } @@ -201,26 +203,12 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { @Test public void testGetSizeForAspectRatio_withOverrideMinSize() { // an initial size with a 1:1 aspect ratio - mPipSizeSpecHandler.setOverrideMinSize(new Size(OVERRIDE_MIN_EDGE_SIZE, - OVERRIDE_MIN_EDGE_SIZE)); - // make sure initial size is same as override min size - Size initSize = mPipSizeSpecHandler.getOverrideMinSize(); + Size initSize = new Size(OVERRIDE_MIN_EDGE_SIZE, OVERRIDE_MIN_EDGE_SIZE); + mSizeSpecSource.setOverrideMinSize(initSize); Size expectedSize = new Size(40, 71); - Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16); + Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16); Assert.assertEquals(expectedSize, actualSize); } - - static class TestPipSizeSpecHandler extends PipSizeSpecHandler { - - TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) { - super(context, displayLayoutState); - } - - @Override - boolean supportsPipSizeLargeScreen() { - return true; - } - } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 85167cb97501..4eb519334e12 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -55,15 +55,16 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm; +import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipAppOpsListener; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; @@ -76,7 +77,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Optional; @@ -109,7 +109,6 @@ public class PipControllerTest extends ShellTestCase { @Mock private PipMotionHelper mMockPipMotionHelper; @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper; @Mock private PipBoundsState mMockPipBoundsState; - @Mock private PipSizeSpecHandler mMockPipSizeSpecHandler; @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState; @Mock private TaskStackListenerImpl mMockTaskStackListener; @Mock private ShellExecutor mMockExecutor; @@ -134,7 +133,7 @@ public class PipControllerTest extends ShellTestCase { mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler, mShellController, mMockDisplayController, mMockPipAnimationController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, - mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState, + mMockPipBoundsState, mMockPipDisplayLayoutState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, @@ -226,7 +225,7 @@ public class PipControllerTest extends ShellTestCase { assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler, mShellController, mMockDisplayController, mMockPipAnimationController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, - mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState, + mMockPipBoundsState, mMockPipDisplayLayoutState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, @@ -330,21 +329,7 @@ public class PipControllerTest extends ShellTestCase { } @Test - public void onKeepClearAreasChanged_featureDisabled_pipBoundsStateDoesntChange() { - mPipController.setEnablePipKeepClearAlgorithm(false); - final int displayId = 1; - final Rect keepClearArea = new Rect(0, 0, 10, 10); - when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId); - - mPipController.mDisplaysChangedListener.onKeepClearAreasChanged( - displayId, Set.of(keepClearArea), Set.of()); - - verify(mMockPipBoundsState, never()).setKeepClearAreas(Mockito.anySet(), Mockito.anySet()); - } - - @Test - public void onKeepClearAreasChanged_featureEnabled_updatesPipBoundsState() { - mPipController.setEnablePipKeepClearAlgorithm(true); + public void onKeepClearAreasChanged_updatesPipBoundsState() { final int displayId = 1; final Rect keepClearArea = new Rect(0, 0, 10, 10); when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java index 8ce3ca4bdc00..0f8db85dcef4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java @@ -29,7 +29,7 @@ import android.graphics.Rect; import android.testing.AndroidTestingRunner; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipBoundsState; import org.junit.Assert; import org.junit.Before; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index 1dfdbf6514ba..6777a5bd8ceb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -36,14 +36,16 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; -import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.PipUiEventLogger; import org.junit.Before; import org.junit.Test; @@ -87,7 +89,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { private PipBoundsState mPipBoundsState; - private PipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; @@ -97,13 +99,14 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); - mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState); + mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); + mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm(); final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm = new PipKeepClearAlgorithmInterface() {}; final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, - mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler); + mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState, + mSizeSpecSource); final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 10b1ddf1b868..9aaabd130527 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -18,7 +18,6 @@ package com.android.wm.shell.pip.phone; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -33,14 +32,16 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; -import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; @@ -92,7 +93,7 @@ public class PipTouchHandlerTest extends ShellTestCase { private PipSnapAlgorithm mPipSnapAlgorithm; private PipMotionHelper mMotionHelper; private PipResizeGestureHandler mPipResizeGestureHandler; - private PipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; private DisplayLayout mDisplayLayout; @@ -108,16 +109,16 @@ public class PipTouchHandlerTest extends ShellTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); - mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState); + mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); + mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); mPipSnapAlgorithm = new PipSnapAlgorithm(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm, - new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler); + new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource); PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator); mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController, - mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer, + mPipBoundsAlgorithm, mPipBoundsState, mSizeSpecSource, mPipTaskOrganizer, pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor); // We aren't actually using ShellInit, so just call init directly mPipTouchHandler.onInit(); @@ -162,8 +163,8 @@ public class PipTouchHandlerTest extends ShellTestCase { // getting the expected min and max size float aspectRatio = (float) mPipBounds.width() / mPipBounds.height(); - Size expectedMinSize = mPipSizeSpecHandler.getMinSize(aspectRatio); - Size expectedMaxSize = mPipSizeSpecHandler.getMaxSize(aspectRatio); + Size expectedMinSize = mSizeSpecSource.getMinSize(aspectRatio); + Size expectedMaxSize = mSizeSpecSource.getMaxSize(aspectRatio); assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds()); verify(mPipResizeGestureHandler, times(1)) @@ -172,16 +173,4 @@ public class PipTouchHandlerTest extends ShellTestCase { verify(mPipResizeGestureHandler, times(1)) .updateMaxSize(expectedMaxSize.getWidth(), expectedMaxSize.getHeight()); } - - @Test - public void updateMovementBounds_withImeAdjustment_movesPip() { - mPipTouchHandler.setEnablePipKeepClearAlgorithm(false); - mFromImeAdjustment = true; - mPipTouchHandler.onImeVisibilityChanged(true /* imeVisible */, mImeHeight); - - mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds, - mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation); - - verify(mMotionHelper, times(1)).animateToOffset(any(), anyInt()); - } } 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 index 02e6b8c71663..45f6c8c7f69f 100644 --- 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 @@ -23,7 +23,9 @@ 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 java.util.Collections.EMPTY_LIST; + +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -34,10 +36,9 @@ 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 com.android.wm.shell.common.pip.PipMediaController; import org.junit.Before; import org.junit.Test; @@ -46,7 +47,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; /** * Unit tests for {@link TvPipActionsProvider} @@ -69,35 +72,38 @@ public class TvPipActionProviderTest extends ShellTestCase { @Mock private PendingIntent mMockPendingIntent; - private RemoteAction createRemoteAction(int identifier) { + private int mNumberOfRemoteActionsCreated = 0; + + private RemoteAction createRemoteAction() { + final int identifier = mNumberOfRemoteActionsCreated++; 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)); + actions.add(createRemoteAction()); } 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; + private void assertActionTypes(List<Integer> expected, List<Integer> actual) { + assertEquals(getActionTypesStrings(expected), getActionTypesStrings(actual)); + } + + private static List<String> getActionTypesStrings(List<Integer> actionTypes) { + return actionTypes.stream().map(a -> TvPipAction.getActionTypeString(a)) + .collect(Collectors.toList()); + } + + private List<Integer> getActionsTypes() { + return mActionsProvider.getActionsList().stream().map(a -> a.getActionType()) + .collect(Collectors.toList()); } @Before public void setUp() { - if (!isTelevision()) { - return; - } + assumeTelevision(); MockitoAnnotations.initMocks(this); mActionsProvider = new TvPipActionsProvider(mContext, mMockPipMediaController, mMockSystemActionsHandler); @@ -105,57 +111,51 @@ public class TvPipActionProviderTest extends ShellTestCase { @Test public void defaultSystemActions_regularPip() { - assumeTelevision(); - mActionsProvider.updateExpansionEnabled(false); - assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), - new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE})); + assertActionTypes(Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE), + getActionsTypes()); } @Test public void defaultSystemActions_expandedPip() { - assumeTelevision(); mActionsProvider.updateExpansionEnabled(true); - assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), - new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE), + getActionsTypes()); } @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})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE), + getActionsTypes()); 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})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE), + getActionsTypes()); 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})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE), + getActionsTypes()); } private void check_expandedPip_updateExpansionState( @@ -167,8 +167,9 @@ public class TvPipActionProviderTest extends ShellTestCase { mActionsProvider.addListener(mMockListener); mActionsProvider.updatePipExpansionState(endExpansion); - assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), - new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE), + getActionsTypes()); if (updateExpected) { verify(mMockListener).onActionsChanged(0, 1, 3); @@ -180,7 +181,6 @@ public class TvPipActionProviderTest extends ShellTestCase { @Test public void expandedPip_toggleExpansion_collapse() { - assumeTelevision(); check_expandedPip_updateExpansionState( /* startExpansion= */ true, /* endExpansion= */ false, @@ -189,7 +189,6 @@ public class TvPipActionProviderTest extends ShellTestCase { @Test public void expandedPip_toggleExpansion_expand() { - assumeTelevision(); check_expandedPip_updateExpansionState( /* startExpansion= */ false, /* endExpansion= */ true, @@ -198,7 +197,6 @@ public class TvPipActionProviderTest extends ShellTestCase { @Test public void expandedPiP_updateExpansionState_alreadyExpanded() { - assumeTelevision(); check_expandedPip_updateExpansionState( /* startExpansion= */ true, /* endExpansion= */ true, @@ -207,7 +205,6 @@ public class TvPipActionProviderTest extends ShellTestCase { @Test public void expandedPiP_updateExpansionState_alreadyCollapsed() { - assumeTelevision(); check_expandedPip_updateExpansionState( /* startExpansion= */ false, /* endExpansion= */ false, @@ -216,8 +213,6 @@ public class TvPipActionProviderTest extends ShellTestCase { @Test public void regularPiP_updateExpansionState_setCollapsed() { - assumeTelevision(); - mActionsProvider.updateExpansionEnabled(false); mActionsProvider.updatePipExpansionState(/* expanded= */ false); mActionsProvider.addListener(mMockListener); @@ -229,153 +224,207 @@ public class TvPipActionProviderTest extends ShellTestCase { @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})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE), + getActionsTypes()); 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})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_CUSTOM, ACTION_MOVE), + getActionsTypes()); 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); + mActionsProvider.setAppActions(EMPTY_LIST, null); - assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), - new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE), + getActionsTypes()); 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)); + mActionsProvider.setAppActions(customActions, createRemoteAction()); - assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), - new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE), + getActionsTypes()); 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); + RemoteAction customClose = createRemoteAction(); 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})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE), + getActionsTypes()); 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})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE), + getActionsTypes()); 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); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_CUSTOM, ACTION_MOVE), + getActionsTypes()); + mActionsProvider.addListener(mMockListener); - mActionsProvider.setAppActions(createRemoteActions(0), null); + mActionsProvider.setAppActions(EMPTY_LIST, null); - assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), - new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, - ACTION_MOVE})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE), + getActionsTypes()); 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)); + mActionsProvider.setAppActions(EMPTY_LIST, createRemoteAction()); - assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), - new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, - ACTION_MOVE})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE), + getActionsTypes()); 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})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE), + getActionsTypes()); } @Test public void mediaActions_hideDisabledActions() { - assumeTelevision(); - mActionsProvider.updateExpansionEnabled(false); + List<RemoteAction> customActions = createRemoteActions(2); + customActions.get(0).setEnabled(false); + mActionsProvider.onMediaActionsChanged(customActions); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE), + getActionsTypes()); + } + + @Test + public void reset_mediaActions() { 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})); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE), + getActionsTypes()); + + mActionsProvider.reset(); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE), + getActionsTypes()); + } + + @Test + public void reset_customActions() { + List<RemoteAction> customActions = createRemoteActions(2); + customActions.get(0).setEnabled(false); + mActionsProvider.setAppActions(customActions, null); + + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE), + getActionsTypes()); + + mActionsProvider.reset(); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE), + getActionsTypes()); + } + + @Test + public void reset_customClose() { + mActionsProvider.setAppActions(EMPTY_LIST, createRemoteAction()); + + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE), + getActionsTypes()); + + mActionsProvider.reset(); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE), + getActionsTypes()); + } + + @Test + public void reset_All() { + mActionsProvider.setAppActions(createRemoteActions(2), createRemoteAction()); + mActionsProvider.onMediaActionsChanged(createRemoteActions(3)); + + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM, + ACTION_MOVE), + getActionsTypes()); + + mActionsProvider.reset(); + assertActionTypes( + Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE), + getActionsTypes()); } } 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 7370ed71bbdd..94f2b9131ebd 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 @@ -25,7 +25,7 @@ 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.common.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 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 index f9b772345b14..974539f23b80 100644 --- 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 @@ -26,9 +26,10 @@ import static org.junit.Assert.assertEquals; import android.view.Gravity; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipSnapAlgorithm; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; +import com.android.wm.shell.common.pip.LegacySizeSpecSource; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.SizeSpecSource; import org.junit.Before; import org.junit.Test; @@ -47,21 +48,20 @@ public class TvPipGravityTest extends ShellTestCase { private TvPipBoundsState mTvPipBoundsState; private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; - private PipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; @Before public void setUp() { - if (!isTelevision()) { - return; - } + assumeTelevision(); + MockitoAnnotations.initMocks(this); mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); - mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler, + mSizeSpecSource = new LegacySizeSpecSource(mContext, mPipDisplayLayoutState); + mTvPipBoundsState = new TvPipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState, - mMockPipSnapAlgorithm, mPipSizeSpecHandler); + mMockPipSnapAlgorithm, mPipDisplayLayoutState, mSizeSpecSource); setRTL(false); } @@ -100,20 +100,22 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void regularPip_defaultGravity() { - assumeTelevision(); checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.RIGHT | Gravity.BOTTOM); } @Test + public void regularPip_defaultTvPipGravity() { + checkGravity(mTvPipBoundsState.getTvPipGravity(), 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); @@ -129,7 +131,6 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void updateGravity_expand_horizontal() { - assumeTelevision(); // Horizontal expanded PiP. mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true); @@ -145,7 +146,6 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void updateGravity_collapse() { - assumeTelevision(); // Vertical expansion mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true); assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.RIGHT, @@ -163,7 +163,6 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void updateGravity_collapse_RTL() { - assumeTelevision(); setRTL(true); // Horizontal expansion @@ -176,7 +175,6 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void updateGravity_expand_collapse() { - assumeTelevision(); // Vertical expanded PiP. mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true); @@ -196,7 +194,6 @@ public class TvPipGravityTest extends ShellTestCase { @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, @@ -229,7 +226,6 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void updateGravity_move_regular_valid() { - assumeTelevision(); mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.RIGHT); // clockwise moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.LEFT, true); @@ -245,7 +241,6 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void updateGravity_move_expanded_valid() { - assumeTelevision(); mTvPipBoundsState.setTvPipExpanded(true); // Vertical expanded PiP. @@ -263,7 +258,6 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void updateGravity_move_regular_invalid() { - assumeTelevision(); int gravity = Gravity.BOTTOM | Gravity.RIGHT; mTvPipBoundsState.setTvPipGravity(gravity); moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false); @@ -287,7 +281,6 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void updateGravity_move_expanded_invalid() { - assumeTelevision(); mTvPipBoundsState.setTvPipExpanded(true); // Vertical expanded PiP. @@ -317,7 +310,6 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void previousCollapsedGravity_defaultValue() { - assumeTelevision(); assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(), mTvPipBoundsState.getDefaultGravity()); setRTL(true); @@ -327,7 +319,6 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void previousCollapsedGravity_changes_on_RTL() { - assumeTelevision(); mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.TOP | Gravity.LEFT); setRTL(true); assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(), 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 aedf65ddc269..998060fdcac2 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 @@ -22,10 +22,10 @@ import android.testing.AndroidTestingRunner import android.util.Size import android.view.Gravity 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.common.pip.PipBoundsState.STASH_TYPE_BOTTOM +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT +import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java index 3a08d32bc430..e26dc7c10989 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java @@ -25,18 +25,24 @@ import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.os.Handler; +import android.os.Looper; import android.view.SurfaceControl; +import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.OnWindowFocusChangeListener; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.SystemWindows; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -50,28 +56,38 @@ public class TvPipMenuControllerTest extends ShellTestCase { @Mock private SystemWindows mMockSystemWindows; @Mock - private SurfaceControl mMockPipLeash; - @Mock - private Handler mMockHandler; - @Mock - private TvPipActionsProvider mMockActionsProvider; - @Mock private TvPipMenuView mMockTvPipMenuView; @Mock private TvPipBackgroundView mMockTvPipBackgroundView; + private Handler mMainHandler; private TvPipMenuController mTvPipMenuController; + private OnWindowFocusChangeListener mFocusChangeListener; @Before public void setUp() { assumeTrue(isTelevision()); MockitoAnnotations.initMocks(this); + mMainHandler = new Handler(Looper.getMainLooper()); + + final ViewTreeObserver mockMenuTreeObserver = mock(ViewTreeObserver.class); + doReturn(mockMenuTreeObserver).when(mMockTvPipMenuView).getViewTreeObserver(); mTvPipMenuController = new TestTvPipMenuController(); mTvPipMenuController.setDelegate(mMockDelegate); - mTvPipMenuController.setTvPipActionsProvider(mMockActionsProvider); - mTvPipMenuController.attach(mMockPipLeash); + mTvPipMenuController.setTvPipActionsProvider(mock(TvPipActionsProvider.class)); + mTvPipMenuController.attach(mock(SurfaceControl.class)); + mFocusChangeListener = captureFocusChangeListener(mockMenuTreeObserver); + } + + private OnWindowFocusChangeListener captureFocusChangeListener( + ViewTreeObserver mockTreeObserver) { + final ArgumentCaptor<OnWindowFocusChangeListener> focusChangeListenerCaptor = + ArgumentCaptor.forClass(OnWindowFocusChangeListener.class); + verify(mockTreeObserver).addOnWindowFocusChangeListener( + focusChangeListenerCaptor.capture()); + return focusChangeListenerCaptor.getValue(); } @Test @@ -81,24 +97,25 @@ public class TvPipMenuControllerTest extends ShellTestCase { @Test public void testSwitch_FromNoMenuMode_ToMoveMode() { - showAndAssertMoveMenu(); + showAndAssertMoveMenu(true); } @Test public void testSwitch_FromNoMenuMode_ToAllActionsMode() { - showAndAssertAllActionsMenu(); + showAndAssertAllActionsMenu(true); } @Test public void testSwitch_FromMoveMode_ToAllActionsMode() { - showAndAssertMoveMenu(); - showAndAssertAllActionsMenu(); + showAndAssertMoveMenu(true); + showAndAssertAllActionsMenu(false); + verify(mMockDelegate, times(2)).onInMoveModeChanged(); } @Test public void testSwitch_FromAllActionsMode_ToMoveMode() { - showAndAssertAllActionsMenu(); - showAndAssertMoveMenu(); + showAndAssertAllActionsMenu(true); + showAndAssertMoveMenu(false); } @Test @@ -110,187 +127,282 @@ public class TvPipMenuControllerTest extends ShellTestCase { @Test public void testCloseMenu_MoveMode() { - showAndAssertMoveMenu(); + showAndAssertMoveMenu(true); - closeMenuAndAssertMenuClosed(); + closeMenuAndAssertMenuClosed(true); verify(mMockDelegate, times(2)).onInMoveModeChanged(); } @Test public void testCloseMenu_AllActionsMode() { - showAndAssertAllActionsMenu(); + showAndAssertAllActionsMenu(true); - closeMenuAndAssertMenuClosed(); + closeMenuAndAssertMenuClosed(true); + } + + @Test + public void testCloseMenu_MoveModeFollowedByMoveMode() { + showAndAssertMoveMenu(true); + showAndAssertMoveMenu(false); + + closeMenuAndAssertMenuClosed(true); + verify(mMockDelegate, times(2)).onInMoveModeChanged(); } @Test public void testCloseMenu_MoveModeFollowedByAllActionsMode() { - showAndAssertMoveMenu(); - showAndAssertAllActionsMenu(); + showAndAssertMoveMenu(true); + showAndAssertAllActionsMenu(false); verify(mMockDelegate, times(2)).onInMoveModeChanged(); - closeMenuAndAssertMenuClosed(); + closeMenuAndAssertMenuClosed(true); } @Test public void testCloseMenu_AllActionsModeFollowedByMoveMode() { - showAndAssertAllActionsMenu(); - showAndAssertMoveMenu(); + showAndAssertAllActionsMenu(true); + showAndAssertMoveMenu(false); - closeMenuAndAssertMenuClosed(); + closeMenuAndAssertMenuClosed(true); verify(mMockDelegate, times(2)).onInMoveModeChanged(); } @Test - public void testExitMoveMode_NoMenuMode() { - mTvPipMenuController.onExitMoveMode(); + public void testCloseMenu_AllActionsModeFollowedByAllActionsMode() { + showAndAssertAllActionsMenu(true); + showAndAssertAllActionsMenu(false); + + closeMenuAndAssertMenuClosed(true); + verify(mMockDelegate, never()).onInMoveModeChanged(); + } + + @Test + public void testExitMenuMode_NoMenuMode() { + mTvPipMenuController.onExitCurrentMenuMode(); assertMenuIsOpen(false); verify(mMockDelegate, never()).onMenuClosed(); + verify(mMockDelegate, never()).onInMoveModeChanged(); } @Test - public void testExitMoveMode_MoveMode() { - showAndAssertMoveMenu(); + public void testExitMenuMode_MoveMode() { + showAndAssertMoveMenu(true); - mTvPipMenuController.onExitMoveMode(); + mTvPipMenuController.onExitCurrentMenuMode(); + mFocusChangeListener.onWindowFocusChanged(false); assertMenuClosed(); verify(mMockDelegate, times(2)).onInMoveModeChanged(); } @Test - public void testExitMoveMode_AllActionsMode() { - showAndAssertAllActionsMenu(); - - mTvPipMenuController.onExitMoveMode(); - assertMenuIsInAllActionsMode(); + public void testExitMenuMode_AllActionsMode() { + showAndAssertAllActionsMenu(true); + mTvPipMenuController.onExitCurrentMenuMode(); + mFocusChangeListener.onWindowFocusChanged(false); + assertMenuClosed(); } @Test - public void testExitMoveMode_AllActionsModeFollowedByMoveMode() { - showAndAssertAllActionsMenu(); - showAndAssertMoveMenu(); + public void testExitMenuMode_AllActionsModeFollowedByMoveMode() { + showAndAssertAllActionsMenu(true); + showAndAssertMoveMenu(false); - mTvPipMenuController.onExitMoveMode(); - assertMenuIsInAllActionsMode(); + mTvPipMenuController.onExitCurrentMenuMode(); + assertSwitchedToAllActionsMode(2); verify(mMockDelegate, times(2)).onInMoveModeChanged(); - verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false)); - verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU)); - } - @Test - public void testOnBackPress_NoMenuMode() { - mTvPipMenuController.onBackPress(); - assertMenuIsOpen(false); - verify(mMockDelegate, never()).onMenuClosed(); + mTvPipMenuController.onExitCurrentMenuMode(); + mFocusChangeListener.onWindowFocusChanged(false); + assertMenuClosed(); } @Test - public void testOnBackPress_MoveMode() { - showAndAssertMoveMenu(); + public void testExitMenuMode_AllActionsModeFollowedByAllActionsMode() { + showAndAssertAllActionsMenu(true); + showAndAssertAllActionsMenu(false); - pressBackAndAssertMenuClosed(); - verify(mMockDelegate, times(2)).onInMoveModeChanged(); + mTvPipMenuController.onExitCurrentMenuMode(); + mFocusChangeListener.onWindowFocusChanged(false); + assertMenuClosed(); + verify(mMockDelegate, never()).onInMoveModeChanged(); } @Test - public void testOnBackPress_AllActionsMode() { - showAndAssertAllActionsMenu(); - - pressBackAndAssertMenuClosed(); - } + public void testExitMenuMode_MoveModeFollowedByAllActionsMode() { + showAndAssertMoveMenu(true); - @Test - public void testOnBackPress_MoveModeFollowedByAllActionsMode() { - showAndAssertMoveMenu(); - showAndAssertAllActionsMenu(); + showAndAssertAllActionsMenu(false); verify(mMockDelegate, times(2)).onInMoveModeChanged(); - pressBackAndAssertMenuClosed(); + mTvPipMenuController.onExitCurrentMenuMode(); + mFocusChangeListener.onWindowFocusChanged(false); + assertMenuClosed(); } @Test - public void testOnBackPress_AllActionsModeFollowedByMoveMode() { - showAndAssertAllActionsMenu(); - showAndAssertMoveMenu(); + public void testExitMenuMode_MoveModeFollowedByMoveMode() { + showAndAssertMoveMenu(true); + showAndAssertMoveMenu(false); - mTvPipMenuController.onBackPress(); - assertMenuIsInAllActionsMode(); + mTvPipMenuController.onExitCurrentMenuMode(); + mFocusChangeListener.onWindowFocusChanged(false); + assertMenuClosed(); verify(mMockDelegate, times(2)).onInMoveModeChanged(); - verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false)); - verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU)); - - pressBackAndAssertMenuClosed(); } @Test public void testOnPipMovement_NoMenuMode() { - assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE)); + moveAndAssertMoveSuccessful(false); } @Test public void testOnPipMovement_MoveMode() { - showAndAssertMoveMenu(); - assertPipMoveSuccessful(true, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE)); - verify(mMockDelegate).movePip(eq(TEST_MOVE_KEYCODE)); + showAndAssertMoveMenu(true); + moveAndAssertMoveSuccessful(true); } @Test public void testOnPipMovement_AllActionsMode() { - showAndAssertAllActionsMenu(); - assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE)); + showAndAssertAllActionsMenu(true); + moveAndAssertMoveSuccessful(false); } @Test - public void testOnPipWindowFocusChanged_NoMenuMode() { - mTvPipMenuController.onPipWindowFocusChanged(false); - assertMenuIsOpen(false); + public void testUnexpectedFocusChanges() { + mFocusChangeListener.onWindowFocusChanged(true); + assertSwitchedToAllActionsMode(1); + + mFocusChangeListener.onWindowFocusChanged(false); + assertMenuClosed(); + + showAndAssertMoveMenu(true); + mFocusChangeListener.onWindowFocusChanged(false); + assertMenuClosed(2); + verify(mMockDelegate, times(2)).onInMoveModeChanged(); } @Test - public void testOnPipWindowFocusChanged_MoveMode() { - showAndAssertMoveMenu(); - mTvPipMenuController.onPipWindowFocusChanged(false); - assertMenuClosed(); + public void testAsyncScenario_AllActionsModeRequestFollowedByAsyncMoveModeRequest() { + mTvPipMenuController.showMenu(); + // Artificially delaying the focus change update and adding a move request to simulate an + // async problematic situation. + mTvPipMenuController.showMovementMenu(); + // The first focus change update arrives + mFocusChangeListener.onWindowFocusChanged(true); + + // We expect that the TvPipMenuController will directly switch to the "pending" menu mode + // - MODE_MOVE_MENU, because no change of focus is needed. + assertSwitchedToMoveMode(); + } + + @Test + public void testAsyncScenario_MoveModeRequestFollowedByAsyncAllActionsModeRequest() { + mTvPipMenuController.showMovementMenu(); + mTvPipMenuController.showMenu(); + + mFocusChangeListener.onWindowFocusChanged(true); + assertSwitchedToAllActionsMode(1); + verify(mMockDelegate, never()).onInMoveModeChanged(); } @Test - public void testOnPipWindowFocusChanged_AllActionsMode() { - showAndAssertAllActionsMenu(); - mTvPipMenuController.onPipWindowFocusChanged(false); + public void testAsyncScenario_DropObsoleteIntermediateModeSwitchRequests() { + mTvPipMenuController.showMovementMenu(); + mTvPipMenuController.closeMenu(); + + // Focus change from showMovementMenu() call. + mFocusChangeListener.onWindowFocusChanged(true); + assertSwitchedToMoveMode(); + verify(mMockDelegate).onInMoveModeChanged(); + + // Focus change from closeMenu() call. + mFocusChangeListener.onWindowFocusChanged(false); assertMenuClosed(); + verify(mMockDelegate, times(2)).onInMoveModeChanged(); + + // Unexpected focus gain should open MODE_ALL_ACTIONS_MENU. + mFocusChangeListener.onWindowFocusChanged(true); + assertSwitchedToAllActionsMode(1); + + mTvPipMenuController.closeMenu(); + mTvPipMenuController.showMovementMenu(); + + assertSwitchedToMoveMode(2); + + mFocusChangeListener.onWindowFocusChanged(false); + assertMenuClosed(2); + + // Closing the menu resets the default menu mode, so the next focus gain opens the menu in + // the default mode - MODE_ALL_ACTIONS_MENU. + mFocusChangeListener.onWindowFocusChanged(true); + assertSwitchedToAllActionsMode(2); + verify(mMockDelegate, times(4)).onInMoveModeChanged(); + } - private void showAndAssertMoveMenu() { + private void showAndAssertMoveMenu(boolean focusChange) { mTvPipMenuController.showMovementMenu(); + if (focusChange) { + mFocusChangeListener.onWindowFocusChanged(true); + } + assertSwitchedToMoveMode(); + } + + private void assertSwitchedToMoveMode() { + assertSwitchedToMoveMode(1); + } + + private void assertSwitchedToMoveMode(int times) { assertMenuIsInMoveMode(); - verify(mMockDelegate).onInMoveModeChanged(); - verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_MOVE_MENU), eq(false)); - verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_MOVE_MENU)); + verify(mMockDelegate, times(2 * times - 1)).onInMoveModeChanged(); + verify(mMockTvPipMenuView, times(times)).transitionToMenuMode(eq(MODE_MOVE_MENU)); + verify(mMockTvPipBackgroundView, times(times)).transitionToMenuMode(eq(MODE_MOVE_MENU)); + } + + private void showAndAssertAllActionsMenu(boolean focusChange) { + showAndAssertAllActionsMenu(focusChange, 1); } - private void showAndAssertAllActionsMenu() { + private void showAndAssertAllActionsMenu(boolean focusChange, int times) { mTvPipMenuController.showMenu(); + if (focusChange) { + mFocusChangeListener.onWindowFocusChanged(true); + } + + assertSwitchedToAllActionsMode(times); + } + + private void assertSwitchedToAllActionsMode(int times) { assertMenuIsInAllActionsMode(); - verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(true)); - verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU)); + verify(mMockTvPipMenuView, times(times)) + .transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU)); + verify(mMockTvPipBackgroundView, times(times)) + .transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU)); } - private void closeMenuAndAssertMenuClosed() { + private void closeMenuAndAssertMenuClosed(boolean focusChange) { mTvPipMenuController.closeMenu(); + if (focusChange) { + mFocusChangeListener.onWindowFocusChanged(false); + } assertMenuClosed(); } - private void pressBackAndAssertMenuClosed() { - mTvPipMenuController.onBackPress(); - assertMenuClosed(); + private void moveAndAssertMoveSuccessful(boolean expectedSuccess) { + mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE); + verify(mMockDelegate, times(expectedSuccess ? 1 : 0)).movePip(eq(TEST_MOVE_KEYCODE)); } private void assertMenuClosed() { + assertMenuClosed(1); + } + + private void assertMenuClosed(int times) { assertMenuIsOpen(false); - verify(mMockDelegate).onMenuClosed(); - verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_NO_MENU), eq(false)); - verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_NO_MENU)); + verify(mMockDelegate, times(times)).onMenuClosed(); + verify(mMockTvPipMenuView, times(times)).transitionToMenuMode(eq(MODE_NO_MENU)); + verify(mMockTvPipBackgroundView, times(times)).transitionToMenuMode(eq(MODE_NO_MENU)); } private void assertMenuIsOpen(boolean open) { @@ -312,15 +424,10 @@ public class TvPipMenuControllerTest extends ShellTestCase { assertMenuIsOpen(true); } - private void assertPipMoveSuccessful(boolean expected, boolean actual) { - assertTrue("Should " + (expected ? "" : "not ") + "move PiP when the menu is in mode " - + mTvPipMenuController.getMenuModeString(), expected == actual); - } - private class TestTvPipMenuController extends TvPipMenuController { TestTvPipMenuController() { - super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMockHandler); + super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMainHandler); } @Override diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 2c69522413d5..40ce7859cc7f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -17,6 +17,7 @@ package com.android.wm.shell.recents; import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; @@ -256,7 +257,7 @@ public class RecentTasksControllerTest extends ShellTestCase { public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() { StaticMockitoSession mockitoSession = mockitoSession().mockStatic( DesktopModeStatus.class).startMocking(); - when(DesktopModeStatus.isProto2Enabled()).thenReturn(true); + when(DesktopModeStatus.isEnabled()).thenReturn(true); ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); @@ -296,7 +297,7 @@ public class RecentTasksControllerTest extends ShellTestCase { public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() { StaticMockitoSession mockitoSession = mockitoSession().mockStatic( DesktopModeStatus.class).startMocking(); - when(DesktopModeStatus.isProto2Enabled()).thenReturn(false); + when(DesktopModeStatus.isEnabled()).thenReturn(false); ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); @@ -367,6 +368,37 @@ public class RecentTasksControllerTest extends ShellTestCase { verify(mRecentTasksController).notifyRecentTasksChanged(); } + @Test + public void getNullSplitBoundsNonSplitTask() { + SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(3); + assertNull("splitBounds should be null for non-split task", sb); + } + + @Test + public void getNullSplitBoundsInvalidTask() { + SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(INVALID_TASK_ID); + assertNull("splitBounds should be null for invalid taskID", sb); + } + + @Test + public void getSplitBoundsForSplitTask() { + SplitBounds pair1Bounds = mock(SplitBounds.class); + SplitBounds pair2Bounds = mock(SplitBounds.class); + + mRecentTasksController.addSplitPair(1, 2, pair1Bounds); + mRecentTasksController.addSplitPair(4, 3, pair2Bounds); + + SplitBounds splitBounds2 = mRecentTasksController.getSplitBoundsForTaskId(2); + SplitBounds splitBounds1 = mRecentTasksController.getSplitBoundsForTaskId(1); + assertEquals("Different splitBounds for same pair", splitBounds1, splitBounds2); + assertEquals(splitBounds1, pair1Bounds); + + SplitBounds splitBounds3 = mRecentTasksController.getSplitBoundsForTaskId(3); + SplitBounds splitBounds4 = mRecentTasksController.getSplitBoundsForTaskId(4); + assertEquals("Different splitBounds for same pair", splitBounds3, splitBounds4); + assertEquals(splitBounds4, pair2Bounds); + } + /** * Helper to create a task with a given task id. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java index 68cb57c14d8c..b1befc46f383 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java @@ -41,6 +41,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + /** Tests for {@link MainStage} */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -61,7 +63,7 @@ public class MainStageTests extends ShellTestCase { MockitoAnnotations.initMocks(this); mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession, mIconProvider); + mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty()); mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java index 3b42a48b5a40..549bd3fcabfb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java @@ -46,6 +46,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import java.util.Optional; + /** Tests for {@link SideStage} */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -66,7 +68,7 @@ public class SideStageTests extends ShellTestCase { MockitoAnnotations.initMocks(this); mRootTask = new TestRunningTaskInfoBuilder().build(); mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession, mIconProvider); + mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty()); mSideStage.onTaskAppeared(mRootTask, mRootLeash); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index fb17d8799bda..99cd4f391153 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -61,9 +62,11 @@ 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.DisplayLayout; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -71,6 +74,7 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.sysui.ShellSharedConstants; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import org.junit.Before; import org.junit.Test; @@ -102,6 +106,9 @@ public class SplitScreenControllerTests extends ShellTestCase { @Mock IconProvider mIconProvider; @Mock StageCoordinator mStageCoordinator; @Mock RecentTasksController mRecentTasks; + @Mock LaunchAdjacentController mLaunchAdjacentController; + @Mock WindowDecorViewModel mWindowDecorViewModel; + @Mock DesktopTasksController mDesktopTasksController; @Captor ArgumentCaptor<Intent> mIntentCaptor; private ShellController mShellController; @@ -117,7 +124,8 @@ public class SplitScreenControllerTests extends ShellTestCase { mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool, - mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator)); + mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel, + mDesktopTasksController, mMainExecutor, mStageCoordinator)); } @Test @@ -211,8 +219,7 @@ public class SplitScreenControllerTests extends ShellTestCase { } @Test - public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() { - doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any()); + public void startIntent_multiInstancesNotSupported_startTaskInBackgroundBeforeSplitActivated() { doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = @@ -230,6 +237,8 @@ public class SplitScreenControllerTests extends ShellTestCase { verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), isNull()); + verify(mSplitScreenController, never()).supportMultiInstancesSplit(any()); + verify(mStageCoordinator, never()).switchSplitPosition(any()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index 4e446c684d86..a3009a55198f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -31,12 +31,14 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder; 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.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.util.Optional; @@ -76,10 +78,13 @@ public class SplitTestUtils { DisplayInsetsController insetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks) { + Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, + Optional<WindowDecorViewModel> windowDecorViewModel) { super(context, displayId, syncQueue, taskOrganizer, mainStage, sideStage, displayController, imeController, insetsController, splitLayout, - transitions, transactionPool, mainExecutor, recentTasks); + transitions, transactionPool, mainExecutor, recentTasks, + launchAdjacentController, windowDecorViewModel); // Prepare root task for testing. mRootTask = new TestRunningTaskInfoBuilder().build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 44c76de945b0..5efd9ad97a3e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -70,6 +70,7 @@ import com.android.wm.shell.TransitionInfoBuilder; 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.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; @@ -77,6 +78,7 @@ import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import org.junit.Before; import org.junit.Test; @@ -101,7 +103,9 @@ public class SplitTransitionTests extends ShellTestCase { @Mock private Transitions mTransitions; @Mock private SurfaceSession mSurfaceSession; @Mock private IconProvider mIconProvider; + @Mock private WindowDecorViewModel mWindowDecorViewModel; @Mock private ShellExecutor mMainExecutor; + @Mock private LaunchAdjacentController mLaunchAdjacentController; @Mock private DefaultMixedHandler mMixedHandler; private SplitLayout mSplitLayout; private MainStage mMainStage; @@ -123,16 +127,17 @@ public class SplitTransitionTests extends ShellTestCase { mSplitLayout = SplitTestUtils.createMockSplitLayout(); mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider)); + mIconProvider, Optional.of(mWindowDecorViewModel))); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mSideStage = spy(new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider)); + mIconProvider, Optional.of(mWindowDecorViewModel))); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, - mTransactionPool, mMainExecutor, Optional.empty()); + mTransactionPool, mMainExecutor, Optional.empty(), + mLaunchAdjacentController, Optional.empty()); mStageCoordinator.setMixedHandler(mMixedHandler); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class)) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 2dcdc74e8ae7..cc4db22e772c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -65,6 +65,7 @@ import com.android.wm.shell.TestShellExecutor; 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.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; @@ -107,6 +108,8 @@ public class StageCoordinatorTests extends ShellTestCase { private DisplayInsetsController mDisplayInsetsController; @Mock private TransactionPool mTransactionPool; + @Mock + private LaunchAdjacentController mLaunchAdjacentController; private final Rect mBounds1 = new Rect(10, 20, 30, 40); private final Rect mBounds2 = new Rect(5, 10, 15, 20); @@ -130,7 +133,7 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, - mMainExecutor, Optional.empty())); + mMainExecutor, Optional.empty(), mLaunchAdjacentController, Optional.empty())); mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build(); when(mSplitLayout.getBounds1()).thenReturn(mBounds1); @@ -344,13 +347,20 @@ public class StageCoordinatorTests extends ShellTestCase { } @Test - public void testExitSplitScreenAfterFolded() { - when(mMainStage.isActive()).thenReturn(true); + public void testExitSplitScreenAfterFoldedAndWakeUp() { when(mMainStage.isFocused()).thenReturn(true); when(mMainStage.getTopVisibleChildTaskId()).thenReturn(INVALID_TASK_ID); + mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build(); + mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build(); + when(mStageCoordinator.isSplitActive()).thenReturn(true); + when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true); mStageCoordinator.onFoldedStateChanged(true); + assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN); + + mStageCoordinator.onFinishedWakingUp(); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull()); } else { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index 1a1bebd28aef..946a7ef7d8c3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -43,6 +43,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import org.junit.Before; import org.junit.Test; @@ -52,6 +53,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + /** * Tests for {@link StageTaskListener} * Build/Install/Run: @@ -71,6 +74,8 @@ public final class StageTaskListenerTests extends ShellTestCase { private SyncTransactionQueue mSyncQueue; @Mock private IconProvider mIconProvider; + @Mock + private WindowDecorViewModel mWindowDecorViewModel; @Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor; private SurfaceSession mSurfaceSession = new SurfaceSession(); @@ -89,7 +94,8 @@ public final class StageTaskListenerTests extends ShellTestCase { mCallbacks, mSyncQueue, mSurfaceSession, - mIconProvider); + mIconProvider, + Optional.of(mWindowDecorViewModel)); mRootTask = new TestRunningTaskInfoBuilder().build(); mRootTask.parentTaskId = INVALID_TASK_ID; mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); @@ -155,7 +161,7 @@ public final class StageTaskListenerTests extends ShellTestCase { childTask.supportsMultiWindow = false; mStageTaskListener.onTaskInfoChanged(childTask); - verify(mCallbacks).onNoLongerSupportMultiWindow(); + verify(mCallbacks).onNoLongerSupportMultiWindow(childTask); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java index 1b389565c066..0088051928fb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java @@ -40,6 +40,7 @@ import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.PendingIntent; import android.content.Context; +import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; import android.testing.AndroidTestingRunner; @@ -220,6 +221,20 @@ public class TaskViewTest extends ShellTestCase { } @Test + public void testSurfaceDestroyed_withTask_shouldNotHideTask_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); + mTaskViewTaskController.setHideTaskWithSurface(false); + + SurfaceHolder sh = mock(SurfaceHolder.class); + mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash); + mTaskView.surfaceCreated(sh); + reset(mViewListener); + mTaskView.surfaceDestroyed(sh); + + verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); + } + + @Test public void testSurfaceDestroyed_withTask_legacyTransitions() { assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); SurfaceHolder sh = mock(SurfaceHolder.class); @@ -563,4 +578,72 @@ public class TaskViewTest extends ShellTestCase { mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash); verify(mTaskViewTaskController, never()).cleanUpPendingTask(); } + + @Test + public void testSetCaptionInsets_noTaskInitially() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + + Rect insets = new Rect(0, 400, 0, 0); + mTaskView.setCaptionInsets(Insets.of(insets)); + mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo()); + + verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded(); + verify(mOrganizer, never()).applyTransaction(any()); + + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + reset(mOrganizer); + reset(mTaskViewTaskController); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskViewTaskController.prepareOpenAnimation(true /* newTask */, + new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo, + mLeash, wct); + mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo()); + + verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded(); + verify(mOrganizer).applyTransaction(any()); + } + + @Test + public void testSetCaptionInsets_withTask() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskViewTaskController.prepareOpenAnimation(true /* newTask */, + new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo, + mLeash, wct); + reset(mTaskViewTaskController); + reset(mOrganizer); + + Rect insets = new Rect(0, 400, 0, 0); + mTaskView.setCaptionInsets(Insets.of(insets)); + mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo()); + verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded(); + verify(mOrganizer).applyTransaction(any()); + } + + @Test + public void testReleaseInOnTaskRemoval_noNPE() { + mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer, + mTaskViewTransitions, mSyncQueue)); + mTaskView = new TaskView(mContext, mTaskViewTaskController); + mTaskView.setListener(mExecutor, new TaskView.Listener() { + @Override + public void onTaskRemovalStarted(int taskId) { + mTaskView.release(); + } + }); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskViewTaskController.prepareOpenAnimation(true /* newTask */, + new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo, + mLeash, wct); + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + + assertThat(mTaskViewTaskController.getTaskInfo()).isEqualTo(mTaskInfo); + + mTaskViewTaskController.prepareCloseAnimation(); + + assertThat(mTaskViewTaskController.getTaskInfo()).isNull(); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java index 03ed18c86608..050443914355 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java @@ -65,12 +65,6 @@ public class TaskViewTransitionsTest extends ShellTestCase { ActivityManager.RunningTaskInfo mTaskInfo; @Mock WindowContainerToken mToken; - @Mock - TaskViewTaskController mTaskViewTaskController2; - @Mock - ActivityManager.RunningTaskInfo mTaskInfo2; - @Mock - WindowContainerToken mToken2; TaskViewTransitions mTaskViewTransitions; @@ -86,16 +80,10 @@ public class TaskViewTransitionsTest extends ShellTestCase { mTaskInfo.token = mToken; mTaskInfo.taskId = 314; mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class); - when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo); - - mTaskInfo2 = new ActivityManager.RunningTaskInfo(); - mTaskInfo2.token = mToken2; - mTaskInfo2.taskId = 315; - mTaskInfo2.taskDescription = mock(ActivityManager.TaskDescription.class); - when(mTaskViewTaskController2.getTaskInfo()).thenReturn(mTaskInfo2); mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions)); mTaskViewTransitions.addTaskView(mTaskViewTaskController); + when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo); } @Test @@ -138,7 +126,7 @@ public class TaskViewTransitionsTest extends ShellTestCase { } @Test - public void testSetTaskBounds_taskVisibleWithPendingOpen_noTransaction() { + public void testSetTaskBounds_taskVisibleWithPending_noTransaction() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true); @@ -154,43 +142,6 @@ public class TaskViewTransitionsTest extends ShellTestCase { } @Test - public void testSetTaskBounds_taskVisibleWithPendingChange_transition() { - assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); - - mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true); - - // Consume the pending transition from visibility change - TaskViewTransitions.PendingTransition pending = - mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); - assertThat(pending).isNotNull(); - mTaskViewTransitions.startAnimation(pending.mClaimed, - mock(TransitionInfo.class), - new SurfaceControl.Transaction(), - new SurfaceControl.Transaction(), - mock(Transitions.TransitionFinishCallback.class)); - // Verify it was consumed - TaskViewTransitions.PendingTransition checkPending = - mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); - assertThat(checkPending).isNull(); - - // Test that set bounds creates a new transition - mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, - new Rect(0, 0, 100, 100)); - assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE)) - .isNotNull(); - - // Test that set bounds again (with different bounds) creates another transition - mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, - new Rect(0, 0, 300, 200)); - List<TaskViewTransitions.PendingTransition> pendingList = - mTaskViewTransitions.findAllPending(mTaskViewTaskController) - .stream() - .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE) - .toList(); - assertThat(pendingList.size()).isEqualTo(2); - } - - @Test public void testSetTaskBounds_sameBounds_noTransaction() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); @@ -217,16 +168,6 @@ public class TaskViewTransitionsTest extends ShellTestCase { mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE); assertThat(pendingBounds).isNotNull(); - // Test that setting same bounds with in-flight transition doesn't cause another one - mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, - new Rect(0, 0, 100, 100)); - List<TaskViewTransitions.PendingTransition> pendingList = - mTaskViewTransitions.findAllPending(mTaskViewTaskController) - .stream() - .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE) - .toList(); - assertThat(pendingList.size()).isEqualTo(1); - // Consume the pending bounds transaction mTaskViewTransitions.startAnimation(pendingBounds.mClaimed, mock(TransitionInfo.class), @@ -246,42 +187,6 @@ public class TaskViewTransitionsTest extends ShellTestCase { assertThat(pendingBounds2).isNull(); } - - @Test - public void testSetTaskBounds_taskVisibleWithDifferentTaskViewPendingChange_transition() { - assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); - - mTaskViewTransitions.addTaskView(mTaskViewTaskController2); - - mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true); - - // Consume the pending transition from visibility change - TaskViewTransitions.PendingTransition pending = - mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); - assertThat(pending).isNotNull(); - mTaskViewTransitions.startAnimation(pending.mClaimed, - mock(TransitionInfo.class), - new SurfaceControl.Transaction(), - new SurfaceControl.Transaction(), - mock(Transitions.TransitionFinishCallback.class)); - // Verify it was consumed - TaskViewTransitions.PendingTransition checkPending = - mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); - assertThat(checkPending).isNull(); - - // Set the second taskview as visible & check that it has a pending transition - mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController2, true); - TaskViewTransitions.PendingTransition pending2 = - mTaskViewTransitions.findPending(mTaskViewTaskController2, TRANSIT_TO_FRONT); - assertThat(pending2).isNotNull(); - - // Test that set bounds on the first taskview will create a new transition - mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, - new Rect(0, 0, 100, 100)); - assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE)) - .isNotNull(); - } - @Test public void testSetTaskVisibility_taskRemoved_noNPE() { mTaskViewTransitions.removeTaskView(mTaskViewTaskController); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index ff380e92322d..a57a7bf4c659 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -99,6 +99,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -176,7 +177,7 @@ public class ShellTransitionTests extends ShellTestCase { assertEquals(1, mDefaultHandler.activeCount()); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any()); } @Test @@ -299,7 +300,7 @@ public class ShellTransitionTests extends ShellTestCase { assertTrue(remoteCalled[0]); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT)); } @Test @@ -449,7 +450,7 @@ public class ShellTransitionTests extends ShellTestCase { assertTrue(remoteCalled[0]); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any()); } @Test @@ -524,20 +525,20 @@ public class ShellTransitionTests extends ShellTestCase { // default handler doesn't merge by default, so it shouldn't increment active count. assertEquals(1, mDefaultHandler.activeCount()); assertEquals(0, mDefaultHandler.mergeCount()); - verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any()); - verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any()); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); // first transition finished - verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any()); - verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any()); // But now the "queued" transition is running assertEquals(1, mDefaultHandler.activeCount()); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any()); } @Test @@ -565,15 +566,15 @@ public class ShellTransitionTests extends ShellTestCase { // it should still only have 1 active, but then show 1 merged assertEquals(1, mDefaultHandler.activeCount()); assertEquals(1, mDefaultHandler.mergeCount()); - verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any()); // We don't tell organizer it is finished yet (since we still want to maintain ordering) - verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any()); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); // transition + merged all finished. - verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any()); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any()); // Make sure nothing was queued assertEquals(0, mDefaultHandler.activeCount()); } @@ -599,8 +600,7 @@ public class ShellTransitionTests extends ShellTestCase { requestStartTransition(transitions, transitTokenNotReady); mDefaultHandler.setSimulateMerge(true); - mDefaultHandler.mFinishes.get(0).second.onTransitionFinished( - null /* wct */, null /* wctCB */); + mDefaultHandler.mFinishes.get(0).second.onTransitionFinished(null /* wct */); // Make sure that the non-ready transition is not merged. assertEquals(0, mDefaultHandler.mergeCount()); @@ -823,8 +823,8 @@ public class ShellTransitionTests extends ShellTestCase { mDefaultHandler.finishAll(); mMainExecutor.flushAll(); // first transition finished - verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any()); - verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any()); // But now the "queued" transition is running assertEquals(1, mDefaultHandler.activeCount()); @@ -835,7 +835,7 @@ public class ShellTransitionTests extends ShellTestCase { mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any()); // runnable2 and runnable3 are executed after the second transition finishes because there // are no other active transitions, runnable1 isn't executed again. @@ -1062,7 +1062,8 @@ public class ShellTransitionTests extends ShellTestCase { mTransactionPool, createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor); final RecentsTransitionHandler recentsHandler = - new RecentsTransitionHandler(shellInit, transitions, null); + new RecentsTransitionHandler(shellInit, transitions, + mock(RecentTasksController.class)); transitions.replaceDefaultHandlerForTest(mDefaultHandler); shellInit.init(); @@ -1449,13 +1450,13 @@ public class ShellTransitionTests extends ShellTestCase { if (mFinishOnSync && info.getType() == TRANSIT_SLEEP) { for (int i = 0; i < mFinishes.size(); ++i) { if (mFinishes.get(i).first != mergeTarget) continue; - mFinishes.remove(i).second.onTransitionFinished(null, null); + mFinishes.remove(i).second.onTransitionFinished(null); return; } } if (!(mSimulateMerge || mShouldMerge.contains(transition))) return; mMerged.add(transition); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); } @Nullable @@ -1478,7 +1479,7 @@ public class ShellTransitionTests extends ShellTestCase { mFinishes; mFinishes = new ArrayList<>(); for (int i = finishes.size() - 1; i >= 0; --i) { - finishes.get(i).second.onTransitionFinished(null /* wct */, null /* wctCB */); + finishes.get(i).second.onTransitionFinished(null /* wct */); } mShouldMerge.clear(); } @@ -1486,7 +1487,7 @@ public class ShellTransitionTests extends ShellTestCase { void finishOne() { Pair<IBinder, Transitions.TransitionFinishCallback> fin = mFinishes.remove(0); mMerged.clear(); - fin.second.onTransitionFinished(null /* wct */, null /* wctCB */); + fin.second.onTransitionFinished(null /* wct */); } int activeCount() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java new file mode 100644 index 000000000000..1d94c9c31de2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java @@ -0,0 +1,280 @@ +/* + * 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.unfold; + +import static android.view.WindowManager.TRANSIT_CHANGE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.os.Binder; +import android.os.IBinder; +import android.view.Display; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; +import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; +import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +public class UnfoldTransitionHandlerTest { + + private UnfoldTransitionHandler mUnfoldTransitionHandler; + + private final TestShellUnfoldProgressProvider mShellUnfoldProgressProvider = + new TestShellUnfoldProgressProvider(); + private final TestTransactionPool mTransactionPool = new TestTransactionPool(); + + private FullscreenUnfoldTaskAnimator mFullscreenUnfoldTaskAnimator; + private SplitTaskUnfoldAnimator mSplitTaskUnfoldAnimator; + private Transitions mTransitions; + + private final IBinder mTransition = new Binder(); + + @Before + public void before() { + final ShellExecutor executor = new TestSyncExecutor(); + final ShellInit shellInit = new ShellInit(executor); + + mFullscreenUnfoldTaskAnimator = mock(FullscreenUnfoldTaskAnimator.class); + mSplitTaskUnfoldAnimator = mock(SplitTaskUnfoldAnimator.class); + mTransitions = mock(Transitions.class); + + mUnfoldTransitionHandler = new UnfoldTransitionHandler( + shellInit, + mShellUnfoldProgressProvider, + mFullscreenUnfoldTaskAnimator, + mSplitTaskUnfoldAnimator, + mTransactionPool, + executor, + mTransitions + ); + + shellInit.init(); + } + + @Test + public void handleRequest_physicalDisplayChange_handlesTransition() { + ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); + TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( + Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); + TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, + triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); + + WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition, + requestInfo); + + assertThat(result).isNotNull(); + } + + @Test + public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() { + ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); + TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( + Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(false); + TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, + triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); + + WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition, + requestInfo); + + assertThat(result).isNull(); + } + + @Test + public void startAnimation_animationHasNotFinishedYet_doesNotFinishTheTransition() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + + verify(finishCallback, never()).onTransitionFinished(any()); + } + + @Test + public void startAnimation_animationFinishes_finishesTheTransition() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeFinished(); + + verify(finishCallback).onTransitionFinished(any()); + } + + @Test + public void startAnimation_animationIsAlreadyFinished_finishesTheTransition() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeFinished(); + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + + verify(finishCallback).onTransitionFinished(any()); + } + + @Test + public void startAnimationSecondTimeAfterFold_animationAlreadyFinished_finishesTransition() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + // First unfold + mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false); + mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeFinished(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + clearInvocations(finishCallback); + + // Fold + mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true); + + // Second unfold + mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false); + mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeFinished(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + + verify(finishCallback).onTransitionFinished(any()); + } + + private TransitionRequestInfo createUnfoldTransitionRequestInfo() { + ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); + TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( + Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); + return new TransitionRequestInfo(TRANSIT_CHANGE, + triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); + } + + private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider, + ShellUnfoldProgressProvider.UnfoldListener { + + private final List<UnfoldListener> mListeners = new ArrayList<>(); + + @Override + public void addListener(Executor executor, UnfoldListener listener) { + mListeners.add(listener); + } + + @Override + public void onFoldStateChanged(boolean isFolded) { + mListeners.forEach(unfoldListener -> unfoldListener.onFoldStateChanged(isFolded)); + } + + @Override + public void onStateChangeFinished() { + mListeners.forEach(UnfoldListener::onStateChangeFinished); + } + + @Override + public void onStateChangeProgress(float progress) { + mListeners.forEach(unfoldListener -> unfoldListener.onStateChangeProgress(progress)); + } + + @Override + public void onStateChangeStarted() { + mListeners.forEach(UnfoldListener::onStateChangeStarted); + } + } + + private static class TestTransactionPool extends TransactionPool { + @Override + public SurfaceControl.Transaction acquire() { + return mock(SurfaceControl.Transaction.class); + } + + @Override + public void release(SurfaceControl.Transaction t) { + } + } + + private static class TestSyncExecutor implements ShellExecutor { + @Override + public void execute(Runnable runnable) { + runnable.run(); + } + + @Override + public void executeDelayed(Runnable runnable, long delayMillis) { + runnable.run(); + } + + @Override + public void removeCallbacks(Runnable runnable) { + } + + @Override + public boolean hasCallback(Runnable runnable) { + return false; + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java index 41bab95b7dd4..d8afe68bac22 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java @@ -18,12 +18,15 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,15 +48,16 @@ import android.view.SurfaceView; import androidx.test.filters.SmallTest; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopTasksController; -import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import org.junit.Before; @@ -82,9 +86,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { @Mock private ShellTaskOrganizer mTaskOrganizer; @Mock private DisplayController mDisplayController; @Mock private DisplayLayout mDisplayLayout; - @Mock private SplitScreenController mSplitScreenController; @Mock private SyncTransactionQueue mSyncQueue; - @Mock private DesktopModeController mDesktopModeController; @Mock private DesktopTasksController mDesktopTasksController; @Mock private InputMonitor mInputMonitor; @Mock private InputManager mInputManager; @@ -92,6 +94,12 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { @Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory; @Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory; @Mock private SurfaceControl.Transaction mTransaction; + @Mock private Display mDisplay; + @Mock private ShellController mShellController; + @Mock private ShellInit mShellInit; + @Mock private DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener + mDesktopModeKeyguardChangeListener; + @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final List<InputManager> mMockInputManagers = new ArrayList<>(); private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel; @@ -101,34 +109,40 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { mMockInputManagers.add(mInputManager); mDesktopModeWindowDecorViewModel = - new DesktopModeWindowDecorViewModel( - mContext, - mMainHandler, - mMainChoreographer, - mTaskOrganizer, - mDisplayController, - mSyncQueue, - mTransitions, - Optional.of(mDesktopModeController), - Optional.of(mDesktopTasksController), - Optional.of(mSplitScreenController), - mDesktopModeWindowDecorFactory, - mMockInputMonitorFactory, - mTransactionFactory - ); + new DesktopModeWindowDecorViewModel( + mContext, + mMainHandler, + mMainChoreographer, + mShellInit, + mTaskOrganizer, + mDisplayController, + mShellController, + mSyncQueue, + mTransitions, + Optional.of(mDesktopTasksController), + mDesktopModeWindowDecorFactory, + mMockInputMonitorFactory, + mTransactionFactory, + mDesktopModeKeyguardChangeListener, + mRootTaskDisplayAreaOrganizer + ); doReturn(mDesktopModeWindowDecoration) - .when(mDesktopModeWindowDecorFactory) - .create(any(), any(), any(), any(), any(), any(), any(), any()); + .when(mDesktopModeWindowDecorFactory) + .create(any(), any(), any(), any(), any(), any(), any(), any(), any()); doReturn(mTransaction).when(mTransactionFactory).get(); doReturn(mDisplayLayout).when(mDisplayController).getDisplayLayout(anyInt()); doReturn(STABLE_INSETS).when(mDisplayLayout).stableInsets(); + doNothing().when(mShellController).addKeyguardChangeListener(any()); when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor); // InputChannel cannot be mocked because it passes to InputEventReceiver. final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG); inputChannels[0].dispose(); when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]); + + mDesktopModeWindowDecoration.mDisplay = mDisplay; + doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId(); } @Test @@ -158,7 +172,8 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { surfaceControl, mMainHandler, mMainChoreographer, - mSyncQueue); + mSyncQueue, + mRootTaskDisplayAreaOrganizer); verify(mDesktopModeWindowDecoration).close(); } @@ -191,7 +206,8 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { surfaceControl, mMainHandler, mMainChoreographer, - mSyncQueue); + mSyncQueue, + mRootTaskDisplayAreaOrganizer); } @Test @@ -254,6 +270,32 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { verify(mInputMonitor, times(1)).dispose(); } + @Test + public void testCaptionIsNotCreatedWhenKeyguardIsVisible() throws Exception { + doReturn(true).when( + mDesktopModeKeyguardChangeListener).isKeyguardVisibleAndOccluded(); + + final int taskId = 1; + final ActivityManager.RunningTaskInfo taskInfo = + createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN); + taskInfo.isFocused = true; + SurfaceControl surfaceControl = mock(SurfaceControl.class); + runOnMainThread(() -> { + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + + mDesktopModeWindowDecorViewModel.onTaskOpening( + taskInfo, surfaceControl, startT, finishT); + + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED); + taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); + mDesktopModeWindowDecorViewModel.onTaskChanging( + taskInfo, surfaceControl, startT, finishT); + }); + verify(mDesktopModeWindowDecorFactory, never()) + .create(any(), any(), any(), any(), any(), any(), any(), any(), any()); + } + private void runOnMainThread(Runnable r) throws Exception { final Handler mainHandler = new Handler(Looper.getMainLooper()); final CountDownLatch latch = new CountDownLatch(1); @@ -267,10 +309,10 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int displayId, @WindowConfiguration.WindowingMode int windowingMode) { ActivityManager.RunningTaskInfo taskInfo = - new TestRunningTaskInfoBuilder() - .setDisplayId(displayId) - .setVisible(true) - .build(); + new TestRunningTaskInfoBuilder() + .setDisplayId(displayId) + .setVisible(true) + .build(); taskInfo.taskId = taskId; taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); return taskInfo; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt index 8f84008e8d2d..3fbab0f9e2bb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt @@ -55,7 +55,7 @@ class DragDetectorTest { fun setUp() { MockitoAnnotations.initMocks(this) - `when`(eventHandler.handleMotionEvent(any())).thenReturn(true) + `when`(eventHandler.handleMotionEvent(any(), any())).thenReturn(true) dragDetector = DragDetector(eventHandler) dragDetector.setTouchSlop(SLOP) @@ -72,13 +72,13 @@ class DragDetectorTest { @Test fun testNoMove_passesDownAndUp() { assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -86,12 +86,12 @@ class DragDetectorTest { @Test fun testMoveInSlop_touch_passesDownAndUp() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN })).thenReturn(false) assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -99,12 +99,12 @@ class DragDetectorTest { val newX = X + SLOP - 1 assertFalse( dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) - verify(eventHandler, never()).handleMotionEvent(argThat { + verify(eventHandler, never()).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_MOVE }) assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -112,13 +112,13 @@ class DragDetectorTest { @Test fun testMoveInSlop_mouse_passesDownMoveAndUp() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_DOWN })).thenReturn(false) assertFalse(dragDetector.onMotionEvent( createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_MOUSE }) @@ -126,14 +126,14 @@ class DragDetectorTest { val newX = X + SLOP - 1 assertTrue(dragDetector.onMotionEvent( createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_MOUSE }) assertTrue(dragDetector.onMotionEvent( createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_MOUSE }) @@ -141,25 +141,25 @@ class DragDetectorTest { @Test fun testMoveBeyondSlop_passesDownMoveAndUp() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_DOWN })).thenReturn(false) assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) val newX = X + SLOP + 1 assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -167,12 +167,12 @@ class DragDetectorTest { @Test fun testPassesHoverEnter() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_HOVER_ENTER })).thenReturn(false) assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y }) } @@ -180,7 +180,7 @@ class DragDetectorTest { @Test fun testPassesHoverMove() { assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y }) } @@ -188,7 +188,7 @@ class DragDetectorTest { @Test fun testPassesHoverExit() { assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y }) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt index de46b31879ed..5c0e04aecf6c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt @@ -76,7 +76,7 @@ class DragPositioningCallbackUtilityTest { minHeight = MIN_HEIGHT defaultMinSize = DEFAULT_MIN displayId = DISPLAY_ID - configuration.windowConfiguration.bounds = STARTING_BOUNDS + configuration.windowConfiguration.setBounds(STARTING_BOUNDS) } mockWindowDecoration.mDisplay = mockDisplay whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt index 69604ddf0af1..c0c4498e3ebf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt @@ -22,6 +22,7 @@ import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFI import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.argThat @@ -71,16 +72,6 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - taskPositioner = - FluidResizeTaskPositioner( - mockShellTaskOrganizer, - mockWindowDecoration, - mockDisplayController, - DISALLOWED_AREA_FOR_END_BOUNDS, - mockDragStartListener, - mockTransactionFactory - ) - whenever(taskToken.asBinder()).thenReturn(taskBinder) whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) @@ -97,10 +88,19 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { minHeight = MIN_HEIGHT defaultMinSize = DEFAULT_MIN displayId = DISPLAY_ID - configuration.windowConfiguration.bounds = STARTING_BOUNDS + configuration.windowConfiguration.setBounds(STARTING_BOUNDS) } mockWindowDecoration.mDisplay = mockDisplay whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID } + + taskPositioner = FluidResizeTaskPositioner( + mockShellTaskOrganizer, + mockWindowDecoration, + mockDisplayController, + mockDragStartListener, + mockTransactionFactory, + DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT + ) } @Test @@ -544,7 +544,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { ) val newX = STARTING_BOUNDS.right.toFloat() + 5 - val newY = STARTING_BOUNDS.top.toFloat() + 5 + val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1 taskPositioner.onDragPositioningMove( newX, newY @@ -614,6 +614,38 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { }) } + @Test + fun testDragResize_drag_taskPositionedInStableBounds() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, // drag + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + val newX = STARTING_BOUNDS.left.toFloat() + val newY = STABLE_BOUNDS.top.toFloat() - 5 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + verify(mockTransaction).setPosition(any(), eq(newX), eq(newY)) + + taskPositioner.onDragPositioningEnd( + newX, + newY + ) + // Verify task's top bound is set to stable bounds top since dragged outside stable bounds + // but not in disallowed end bounds area. + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && + change.configuration.windowConfiguration.bounds.top == + STABLE_BOUNDS.top + } + }) + } + companion object { private const val TASK_ID = 5 private const val MIN_WIDTH = 10 @@ -622,10 +654,11 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { private const val DEFAULT_MIN = 40 private const val DISPLAY_ID = 1 private const val NAVBAR_HEIGHT = 50 + private const val CAPTION_HEIGHT = 50 + private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) - private val STARTING_BOUNDS = Rect(0, 0, 100, 100) + private val STARTING_BOUNDS = Rect(100, 100, 200, 200) private val STABLE_INSETS = Rect(0, 50, 0, 0) - private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 300, 300) private val DISALLOWED_RESIZE_AREA = Rect( DISPLAY_BOUNDS.left, DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT, @@ -633,7 +666,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { DISPLAY_BOUNDS.bottom) private val STABLE_BOUNDS = Rect( DISPLAY_BOUNDS.left, - DISPLAY_BOUNDS.top, + DISPLAY_BOUNDS.top + CAPTION_HEIGHT, DISPLAY_BOUNDS.right, DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT ) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index 4147dd8e6152..8913453aa578 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -89,17 +89,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - taskPositioner = - VeiledResizeTaskPositioner( - mockShellTaskOrganizer, - mockDesktopWindowDecoration, - mockDisplayController, - DISALLOWED_AREA_FOR_END_BOUNDS, - mockDragStartListener, - mockTransactionFactory, - mockTransitions - ) - whenever(taskToken.asBinder()).thenReturn(taskBinder) whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) @@ -115,10 +104,21 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { minHeight = MIN_HEIGHT defaultMinSize = DEFAULT_MIN displayId = DISPLAY_ID - configuration.windowConfiguration.bounds = STARTING_BOUNDS + configuration.windowConfiguration.setBounds(STARTING_BOUNDS) } mockDesktopWindowDecoration.mDisplay = mockDisplay whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID } + + taskPositioner = + VeiledResizeTaskPositioner( + mockShellTaskOrganizer, + mockDesktopWindowDecoration, + mockDisplayController, + mockDragStartListener, + mockTransactionFactory, + mockTransitions, + DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT + ) } @Test @@ -269,7 +269,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { ) val newX = STARTING_BOUNDS.left.toFloat() + 5 - val newY = STARTING_BOUNDS.top.toFloat() + 5 + val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1 taskPositioner.onDragPositioningMove( newX, newY @@ -334,6 +334,38 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { }) } + @Test + fun testDragResize_drag_taskPositionedInStableBounds() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, // drag + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + val newX = STARTING_BOUNDS.left.toFloat() + val newY = STABLE_BOUNDS.top.toFloat() - 5 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + verify(mockTransaction).setPosition(any(), eq(newX), eq(newY)) + + taskPositioner.onDragPositioningEnd( + newX, + newY + ) + // Verify task's top bound is set to stable bounds top since dragged outside stable bounds + // but not in disallowed end bounds area. + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && + change.configuration.windowConfiguration.bounds.top == + STABLE_BOUNDS.top + } + }) + } + companion object { private const val TASK_ID = 5 private const val MIN_WIDTH = 10 @@ -342,12 +374,13 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { private const val DEFAULT_MIN = 40 private const val DISPLAY_ID = 1 private const val NAVBAR_HEIGHT = 50 + private const val CAPTION_HEIGHT = 50 + private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) - private val STARTING_BOUNDS = Rect(0, 0, 100, 100) - private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 50, 50) + private val STARTING_BOUNDS = Rect(100, 100, 200, 200) private val STABLE_BOUNDS = Rect( DISPLAY_BOUNDS.left, - DISPLAY_BOUNDS.top, + DISPLAY_BOUNDS.top + CAPTION_HEIGHT, DISPLAY_BOUNDS.right, DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT ) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 5a2326b9c393..76bc25aa66ef 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Point; @@ -57,7 +58,6 @@ import android.window.SurfaceSyncGroup; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; -import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; @@ -117,6 +117,7 @@ public class WindowDecorationTests extends ShellTestCase { private SurfaceControl.Transaction mMockSurfaceControlFinishT; private SurfaceControl.Transaction mMockSurfaceControlAddWindowT; private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams(); + private Configuration mWindowConfiguration = new Configuration(); private int mCaptionMenuWidthId; private int mCaptionMenuShadowRadiusId; private int mCaptionMenuCornerRadiusId; @@ -297,6 +298,7 @@ public class WindowDecorationTests extends ShellTestCase { taskInfo.isFocused = true; // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; + mWindowConfiguration.densityDpi = taskInfo.configuration.densityDpi; final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -316,7 +318,8 @@ public class WindowDecorationTests extends ShellTestCase { releaseOrder.verify(t).remove(captionContainerSurface); releaseOrder.verify(t).remove(decorContainerSurface); releaseOrder.verify(t).apply(); - verify(mMockWindowContainerTransaction) + // Expect to remove two insets sources, the caption insets and the mandatory gesture insets. + verify(mMockWindowContainerTransaction, Mockito.times(2)) .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt()); } @@ -410,15 +413,17 @@ public class WindowDecorationTests extends ShellTestCase { verify(additionalWindowSurfaceBuilder).build(); verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0); final int width = WindowDecoration.loadDimensionPixelSize( - mContext.getResources(), mCaptionMenuWidthId); + windowDecor.mDecorWindowContext.getResources(), mCaptionMenuWidthId); final int height = WindowDecoration.loadDimensionPixelSize( - mContext.getResources(), mRelayoutParams.mCaptionHeightId); + windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId); verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height); - final int shadowRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(), + final int shadowRadius = WindowDecoration.loadDimensionPixelSize( + windowDecor.mDecorWindowContext.getResources(), mCaptionMenuShadowRadiusId); verify(mMockSurfaceControlAddWindowT) .setShadowRadius(additionalWindowSurface, shadowRadius); - final int cornerRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(), + final int cornerRadius = WindowDecoration.loadDimensionPixelSize( + windowDecor.mDecorWindowContext.getResources(), mCaptionMenuCornerRadiusId); verify(mMockSurfaceControlAddWindowT) .setCornerRadius(additionalWindowSurface, cornerRadius); @@ -513,9 +518,8 @@ public class WindowDecorationTests extends ShellTestCase { private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { - return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(), - mMockDisplayController, mMockShellTaskOrganizer, - taskInfo, testSurface, + return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, + taskInfo, testSurface, mWindowConfiguration, new MockObjectSupplier<>(mMockSurfaceControlBuilders, () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))), new MockObjectSupplier<>(mMockSurfaceControlTransactions, @@ -555,13 +559,15 @@ public class WindowDecorationTests extends ShellTestCase { TestWindowDecoration(Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, + Configuration windowConfiguration, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, SurfaceControlViewHostFactory surfaceControlViewHostFactory) { super(context, displayController, taskOrganizer, taskInfo, taskSurface, - surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, - windowContainerTransactionSupplier, surfaceControlViewHostFactory); + windowConfiguration, surfaceControlBuilderSupplier, + surfaceControlTransactionSupplier, windowContainerTransactionSupplier, + surfaceControlViewHostFactory); } @Override diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 28bda72bccdd..47a7f3579764 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -85,7 +85,10 @@ cc_library { export_include_dirs: ["include"], export_shared_lib_headers: ["libz"], static_libs: ["libincfs-utils"], - whole_static_libs: ["libincfs-utils"], + whole_static_libs: [ + "libandroidfw_pathutils", + "libincfs-utils", + ], export_static_lib_headers: ["libincfs-utils"], target: { android: { @@ -137,6 +140,28 @@ cc_library { }, } +cc_library_static { + name: "libandroidfw_pathutils", + defaults: ["libandroidfw_defaults"], + host_supported: true, + export_include_dirs: ["include_pathutils"], + srcs: [ + "PathUtils.cpp", + ], + shared_libs: [ + "libutils", + ], + target: { + windows: { + enabled: true, + }, + }, + visibility: [ + ":__subpackages__", + "//frameworks/base/tools/aapt", + ], +} + common_test_libs = [ "libandroidfw", "libbase", @@ -232,6 +257,7 @@ cc_benchmark { "tests/AssetManager2_bench.cpp", "tests/AttributeResolution_bench.cpp", "tests/CursorWindow_bench.cpp", + "tests/Generic_bench.cpp", "tests/SparseEntry_bench.cpp", "tests/Theme_bench.cpp", ], diff --git a/libs/androidfw/ApkParsing.cpp b/libs/androidfw/ApkParsing.cpp index 32d2c5b05acb..7eedfdb5c921 100644 --- a/libs/androidfw/ApkParsing.cpp +++ b/libs/androidfw/ApkParsing.cpp @@ -56,6 +56,11 @@ const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit, return nullptr; } + // Make sure file starts with 'lib/' prefix. + if (strncmp(fileName, APK_LIB.data(), APK_LIB_LEN) != 0) { + return nullptr; + } + // Make sure there aren't subdirectories by checking if the next / after lib/ is the last slash if (memchr(fileName + APK_LIB_LEN, '/', fileNameLen - APK_LIB_LEN) != lastSlash) { return nullptr; diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index fb2b57193b83..68befffecf2f 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -26,6 +26,7 @@ #include <androidfw/AssetDir.h> #include <androidfw/AssetManager.h> #include <androidfw/misc.h> +#include <androidfw/PathUtils.h> #include <androidfw/ResourceTypes.h> #include <androidfw/ZipFileRO.h> #include <cutils/atomic.h> @@ -88,10 +89,10 @@ String8 idmapPathForPackagePath(const String8& pkgPath) { const char* root = getenv("ANDROID_DATA"); LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set"); String8 path(root); - path.appendPath(kResourceCache); + appendPath(path, kResourceCache); char buf[256]; // 256 chars should be enough for anyone... - strncpy(buf, pkgPath.string(), 255); + strncpy(buf, pkgPath.c_str(), 255); buf[255] = '\0'; char* filename = buf; while (*filename && *filename == '/') { @@ -104,7 +105,7 @@ String8 idmapPathForPackagePath(const String8& pkgPath) { } ++p; } - path.appendPath(filename); + appendPath(path, filename); path.append("@idmap"); return path; @@ -181,17 +182,17 @@ bool AssetManager::addAssetPath( String8 realPath(path); if (kAppZipName) { - realPath.appendPath(kAppZipName); + appendPath(realPath, kAppZipName); } - ap.type = ::getFileType(realPath.string()); + ap.type = ::getFileType(realPath.c_str()); if (ap.type == kFileTypeRegular) { ap.path = realPath; } else { ap.path = path; - ap.type = ::getFileType(path.string()); + ap.type = ::getFileType(path.c_str()); if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) { ALOGW("Asset path %s is neither a directory nor file (type=%d).", - path.string(), (int)ap.type); + path.c_str(), (int)ap.type); return false; } } @@ -207,7 +208,7 @@ bool AssetManager::addAssetPath( } ALOGV("In %p Asset %s path: %s", this, - ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); + ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.c_str()); ap.isSystemAsset = isSystemAsset; ssize_t apPos = mAssetPaths.add(ap); @@ -248,7 +249,7 @@ bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie) Asset* idmap = NULL; if ((idmap = openAssetFromFileLocked(idmapPath, Asset::ACCESS_BUFFER)) == NULL) { - ALOGW("failed to open idmap file %s\n", idmapPath.string()); + ALOGW("failed to open idmap file %s\n", idmapPath.c_str()); return false; } @@ -256,7 +257,7 @@ bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie) String8 overlayPath; if (!ResTable::getIdmapInfo(idmap->getBuffer(false), idmap->getLength(), NULL, NULL, NULL, &targetPath, &overlayPath)) { - ALOGW("failed to read idmap file %s\n", idmapPath.string()); + ALOGW("failed to read idmap file %s\n", idmapPath.c_str()); delete idmap; return false; } @@ -264,29 +265,29 @@ bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie) if (overlayPath != packagePath) { ALOGW("idmap file %s inconcistent: expected path %s does not match actual path %s\n", - idmapPath.string(), packagePath.string(), overlayPath.string()); + idmapPath.c_str(), packagePath.c_str(), overlayPath.c_str()); return false; } - if (access(targetPath.string(), R_OK) != 0) { - ALOGW("failed to access file %s: %s\n", targetPath.string(), strerror(errno)); + if (access(targetPath.c_str(), R_OK) != 0) { + ALOGW("failed to access file %s: %s\n", targetPath.c_str(), strerror(errno)); return false; } - if (access(idmapPath.string(), R_OK) != 0) { - ALOGW("failed to access file %s: %s\n", idmapPath.string(), strerror(errno)); + if (access(idmapPath.c_str(), R_OK) != 0) { + ALOGW("failed to access file %s: %s\n", idmapPath.c_str(), strerror(errno)); return false; } - if (access(overlayPath.string(), R_OK) != 0) { - ALOGW("failed to access file %s: %s\n", overlayPath.string(), strerror(errno)); + if (access(overlayPath.c_str(), R_OK) != 0) { + ALOGW("failed to access file %s: %s\n", overlayPath.c_str(), strerror(errno)); return false; } asset_path oap; oap.path = overlayPath; - oap.type = ::getFileType(overlayPath.string()); + oap.type = ::getFileType(overlayPath.c_str()); oap.idmap = idmapPath; #if 0 ALOGD("Overlay added: targetPath=%s overlayPath=%s idmapPath=%s\n", - targetPath.string(), overlayPath.string(), idmapPath.string()); + targetPath.c_str(), overlayPath.c_str(), idmapPath.c_str()); #endif mAssetPaths.add(oap); *cookie = static_cast<int32_t>(mAssetPaths.size()); @@ -310,7 +311,7 @@ bool AssetManager::addAssetFd( ap.type = kFileTypeRegular; ap.assumeOwnership = assume_ownership; - ALOGV("In %p Asset fd %d name: %s", this, fd, ap.path.string()); + ALOGV("In %p Asset fd %d name: %s", this, fd, ap.path.c_str()); ssize_t apPos = mAssetPaths.add(ap); @@ -343,11 +344,11 @@ bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApk assets[i] = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); if (assets[i] == NULL) { - ALOGW("failed to find resources.arsc in %s\n", ap.path.string()); + ALOGW("failed to find resources.arsc in %s\n", ap.path.c_str()); goto exit; } if (tables[i].add(assets[i]) != NO_ERROR) { - ALOGW("failed to add %s to resource table", paths[i].string()); + ALOGW("failed to add %s to resource table", paths[i].c_str()); goto exit; } } @@ -367,7 +368,7 @@ bool AssetManager::addDefaultAssets() LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set"); String8 path(root); - path.appendPath(kSystemAssets); + appendPath(path, kSystemAssets); return addAssetPath(path, NULL, false /* appAsLib */, true /* isSystemAsset */); } @@ -439,7 +440,7 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode) LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); String8 assetName(kAssetsRoot); - assetName.appendPath(fileName); + appendPath(assetName, fileName); /* * For each top-level asset path, search for the asset. @@ -449,8 +450,8 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode) while (i > 0) { i--; ALOGV("Looking for asset '%s' in '%s'\n", - assetName.string(), mAssetPaths.itemAt(i).path.string()); - Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, + assetName.c_str(), mAssetPaths.itemAt(i).path.c_str()); + Asset* pAsset = openNonAssetInPathLocked(assetName.c_str(), mode, mAssetPaths.editItemAt(i)); if (pAsset != NULL) { return pAsset != kExcludedAsset ? pAsset : NULL; @@ -478,7 +479,7 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode, int32_t size_t i = mAssetPaths.size(); while (i > 0) { i--; - ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string()); + ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.c_str()); Asset* pAsset = openNonAssetInPathLocked( fileName, mode, mAssetPaths.editItemAt(i)); if (pAsset != NULL) { @@ -500,7 +501,7 @@ Asset* AssetManager::openNonAsset(const int32_t cookie, const char* fileName, Ac if (which < mAssetPaths.size()) { ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, - mAssetPaths.itemAt(which).path.string()); + mAssetPaths.itemAt(which).path.c_str()); Asset* pAsset = openNonAssetInPathLocked( fileName, mode, mAssetPaths.editItemAt(which)); if (pAsset != NULL) { @@ -546,10 +547,10 @@ bool AssetManager::appendPathToResTable(asset_path& ap, bool appAsLib) const { ResTable* sharedRes = NULL; bool shared = true; bool onlyEmptyResources = true; - ATRACE_NAME(ap.path.string()); + ATRACE_NAME(ap.path.c_str()); Asset* idmap = openIdmapLocked(ap); size_t nextEntryIdx = mResources->getTableCount(); - ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); + ALOGV("Looking for resource asset in '%s'\n", ap.path.c_str()); if (ap.type != kFileTypeDirectory && ap.rawFd < 0) { if (nextEntryIdx == 0) { // The first item is typically the framework resources, @@ -565,7 +566,7 @@ bool AssetManager::appendPathToResTable(asset_path& ap, bool appAsLib) const { ass = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTableAsset(ap.path); if (ass == NULL) { - ALOGV("loading resource table %s\n", ap.path.string()); + ALOGV("loading resource table %s\n", ap.path.c_str()); ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, @@ -580,23 +581,23 @@ bool AssetManager::appendPathToResTable(asset_path& ap, bool appAsLib) const { // If this is the first resource table in the asset // manager, then we are going to cache it so that we // can quickly copy it out for others. - ALOGV("Creating shared resources for %s", ap.path.string()); + ALOGV("Creating shared resources for %s", ap.path.c_str()); sharedRes = new ResTable(); sharedRes->add(ass, idmap, nextEntryIdx + 1, false); #ifdef __ANDROID__ const char* data = getenv("ANDROID_DATA"); LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set"); String8 overlaysListPath(data); - overlaysListPath.appendPath(kResourceCache); - overlaysListPath.appendPath("overlays.list"); - addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx); + appendPath(overlaysListPath, kResourceCache); + appendPath(overlaysListPath, "overlays.list"); + addSystemOverlays(overlaysListPath.c_str(), ap.path, sharedRes, nextEntryIdx); #endif sharedRes = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTable(ap.path, sharedRes); } } } else { - ALOGV("loading resource table %s\n", ap.path.string()); + ALOGV("loading resource table %s\n", ap.path.c_str()); ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, @@ -607,10 +608,10 @@ bool AssetManager::appendPathToResTable(asset_path& ap, bool appAsLib) const { if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); if (sharedRes != NULL) { - ALOGV("Copying existing resources for %s", ap.path.string()); + ALOGV("Copying existing resources for %s", ap.path.c_str()); mResources->add(sharedRes, ap.isSystemAsset); } else { - ALOGV("Parsing resources for %s", ap.path.string()); + ALOGV("Parsing resources for %s", ap.path.c_str()); mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib, ap.isSystemAsset); } onlyEmptyResources = false; @@ -692,9 +693,9 @@ Asset* AssetManager::openIdmapLocked(const struct asset_path& ap) const ass = const_cast<AssetManager*>(this)-> openAssetFromFileLocked(ap.idmap, Asset::ACCESS_BUFFER); if (ass) { - ALOGV("loading idmap %s\n", ap.idmap.string()); + ALOGV("loading idmap %s\n", ap.idmap.c_str()); } else { - ALOGW("failed to load idmap %s\n", ap.idmap.string()); + ALOGW("failed to load idmap %s\n", ap.idmap.c_str()); } } return ass; @@ -789,7 +790,7 @@ Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode m /* look at the filesystem on disk */ if (ap.type == kFileTypeDirectory) { String8 path(ap.path); - path.appendPath(fileName); + appendPath(path, fileName); pAsset = openAssetFromFileLocked(path, mode); @@ -811,10 +812,10 @@ Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode m /* check the appropriate Zip file */ ZipFileRO* pZip = getZipFileLocked(ap); if (pZip != NULL) { - ALOGV("GOT zip, checking NA '%s'", (const char*) path); - ZipEntryRO entry = pZip->findEntryByName(path.string()); + ALOGV("GOT zip, checking NA '%s'", path.c_str()); + ZipEntryRO entry = pZip->findEntryByName(path.c_str()); if (entry != NULL) { - ALOGV("FOUND NA in Zip file for %s", (const char*) path); + ALOGV("FOUND NA in Zip file for %s", path.c_str()); pAsset = openAssetFromZipLocked(pZip, entry, mode, path); pZip->releaseEntry(entry); } @@ -823,7 +824,7 @@ Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode m if (pAsset != NULL) { /* create a "source" name, for debug/display */ pAsset->setAssetSource( - createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""), + createZipSourceNameLocked(ZipSet::getPathName(ap.path.c_str()), String8(""), String8(fileName))); } } @@ -841,9 +842,9 @@ String8 AssetManager::createZipSourceNameLocked(const String8& zipFileName, sourceName.append(zipFileName); sourceName.append(":"); if (dirName.length() > 0) { - sourceName.appendPath(dirName); + appendPath(sourceName, dirName); } - sourceName.appendPath(fileName); + appendPath(sourceName, fileName); return sourceName; } @@ -853,7 +854,7 @@ String8 AssetManager::createZipSourceNameLocked(const String8& zipFileName, String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* rootDir) { String8 path(ap.path); - if (rootDir != NULL) path.appendPath(rootDir); + if (rootDir != NULL) appendPath(path, rootDir); return path; } @@ -870,7 +871,7 @@ ZipFileRO* AssetManager::getZipFileLocked(asset_path& ap) } if (ap.rawFd < 0) { - ALOGV("getZipFileLocked: Creating new zip from path %s", ap.path.string()); + ALOGV("getZipFileLocked: Creating new zip from path %s", ap.path.c_str()); ap.zip = mZipSet.getSharedZip(ap.path); } else { ALOGV("getZipFileLocked: Creating new zip from fd %d", ap.rawFd); @@ -897,12 +898,12 @@ Asset* AssetManager::openAssetFromFileLocked(const String8& pathName, { Asset* pAsset = NULL; - if (strcasecmp(pathName.getPathExtension().string(), ".gz") == 0) { + if (strcasecmp(getPathExtension(pathName).c_str(), ".gz") == 0) { //printf("TRYING '%s'\n", (const char*) pathName); - pAsset = Asset::createFromCompressedFile(pathName.string(), mode); + pAsset = Asset::createFromCompressedFile(pathName.c_str(), mode); } else { //printf("TRYING '%s'\n", (const char*) pathName); - pAsset = Asset::createFromFile(pathName.string(), mode); + pAsset = Asset::createFromFile(pathName.c_str(), mode); } return pAsset; @@ -940,12 +941,12 @@ Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile, if (method == ZipFileRO::kCompressStored) { pAsset = Asset::createFromUncompressedMap(std::move(*dataMap), mode); - ALOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(), + ALOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.c_str(), dataMap->file_name(), mode, pAsset.get()); } else { pAsset = Asset::createFromCompressedMap(std::move(*dataMap), static_cast<size_t>(uncompressedLen), mode); - ALOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(), + ALOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.c_str(), dataMap->file_name(), mode, pAsset.get()); } if (pAsset == NULL) { @@ -993,10 +994,10 @@ AssetDir* AssetManager::openDir(const char* dirName) i--; const asset_path& ap = mAssetPaths.itemAt(i); if (ap.type == kFileTypeRegular) { - ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); + ALOGV("Adding directory %s from zip %s", dirName, ap.path.c_str()); scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName); } else { - ALOGV("Adding directory %s from dir %s", dirName, ap.path.string()); + ALOGV("Adding directory %s from dir %s", dirName, ap.path.c_str()); scanAndMergeDirLocked(pMergedInfo, ap, kAssetsRoot, dirName); } } @@ -1042,10 +1043,10 @@ AssetDir* AssetManager::openNonAssetDir(const int32_t cookie, const char* dirNam if (which < mAssetPaths.size()) { const asset_path& ap = mAssetPaths.itemAt(which); if (ap.type == kFileTypeRegular) { - ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); + ALOGV("Adding directory %s from zip %s", dirName, ap.path.c_str()); scanAndMergeZipLocked(pMergedInfo, ap, NULL, dirName); } else { - ALOGV("Adding directory %s from dir %s", dirName, ap.path.string()); + ALOGV("Adding directory %s from dir %s", dirName, ap.path.c_str()); scanAndMergeDirLocked(pMergedInfo, ap, NULL, dirName); } } @@ -1075,11 +1076,10 @@ bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMerg { assert(pMergedInfo != NULL); - //printf("scanAndMergeDir: %s %s %s\n", ap.path.string(), rootDir, dirName); + //printf("scanAndMergeDir: %s %s %s\n", ap.path.c_str(), rootDir, dirName); String8 path = createPathNameLocked(ap, rootDir); - if (dirName[0] != '\0') - path.appendPath(dirName); + if (dirName[0] != '\0') appendPath(path, dirName); SortedVector<AssetDir::FileInfo>* pContents = scanDirLocked(path); if (pContents == NULL) @@ -1100,7 +1100,7 @@ bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMerg const char* name; int nameLen; - name = pContents->itemAt(i).getFileName().string(); + name = pContents->itemAt(i).getFileName().c_str(); nameLen = strlen(name); if (nameLen > exclExtLen && strcmp(name + (nameLen - exclExtLen), kExcludeExtension) == 0) @@ -1111,8 +1111,8 @@ bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMerg matchIdx = AssetDir::FileInfo::findEntry(pMergedInfo, match); if (matchIdx > 0) { ALOGV("Excluding '%s' [%s]\n", - pMergedInfo->itemAt(matchIdx).getFileName().string(), - pMergedInfo->itemAt(matchIdx).getSourceName().string()); + pMergedInfo->itemAt(matchIdx).getFileName().c_str(), + pMergedInfo->itemAt(matchIdx).getSourceName().c_str()); pMergedInfo->removeAt(matchIdx); } else { //printf("+++ no match on '%s'\n", (const char*) match); @@ -1150,9 +1150,9 @@ SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& pat struct dirent* entry; FileType fileType; - ALOGV("Scanning dir '%s'\n", path.string()); + ALOGV("Scanning dir '%s'\n", path.c_str()); - dir = opendir(path.string()); + dir = opendir(path.c_str()); if (dir == NULL) return NULL; @@ -1176,7 +1176,7 @@ SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& pat fileType = kFileTypeUnknown; #else // stat the file - fileType = ::getFileType(path.appendPathCopy(entry->d_name).string()); + fileType = ::getFileType(appendPathCopy(path, entry->d_name).c_str()); #endif if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory) @@ -1184,9 +1184,9 @@ SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& pat AssetDir::FileInfo info; info.set(String8(entry->d_name), fileType); - if (strcasecmp(info.getFileName().getPathExtension().string(), ".gz") == 0) - info.setFileName(info.getFileName().getBasePath()); - info.setSourceName(path.appendPathCopy(info.getFileName())); + if (strcasecmp(getPathExtension(info.getFileName()).c_str(), ".gz") == 0) + info.setFileName(getBasePath(info.getFileName())); + info.setSourceName(appendPathCopy(path, info.getFileName())); pContents->add(info); } @@ -1212,15 +1212,15 @@ bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMerg pZip = mZipSet.getZip(ap.path); if (pZip == NULL) { - ALOGW("Failure opening zip %s\n", ap.path.string()); + ALOGW("Failure opening zip %s\n", ap.path.c_str()); return false; } - zipName = ZipSet::getPathName(ap.path.string()); + zipName = ZipSet::getPathName(ap.path.c_str()); /* convert "sounds" to "rootDir/sounds" */ if (rootDir != NULL) dirName = rootDir; - dirName.appendPath(baseDirName); + appendPath(dirName, baseDirName); /* * Scan through the list of files, looking for a match. The files in @@ -1240,7 +1240,7 @@ bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMerg */ int dirNameLen = dirName.length(); void *iterationCookie; - if (!pZip->startIteration(&iterationCookie, dirName.string(), NULL)) { + if (!pZip->startIteration(&iterationCookie, dirName.c_str(), NULL)) { ALOGW("ZipFileRO::startIteration returned false"); return false; } @@ -1254,7 +1254,7 @@ bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMerg ALOGE("ARGH: name too long?\n"); continue; } - //printf("Comparing %s in %s?\n", nameBuf, dirName.string()); + //printf("Comparing %s in %s?\n", nameBuf, dirName.c_str()); if (dirNameLen == 0 || nameBuf[dirNameLen] == '/') { const char* cp; @@ -1269,13 +1269,13 @@ bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMerg if (nextSlash == NULL) { /* this is a file in the requested directory */ - info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular); + info.set(getPathLeaf(String8(nameBuf)), kFileTypeRegular); info.setSourceName( createZipSourceNameLocked(zipName, dirName, info.getFileName())); contents.add(info); - //printf("FOUND: file '%s'\n", info.getFileName().string()); + //printf("FOUND: file '%s'\n", info.getFileName().c_str()); } else { /* this is a subdir; add it if we don't already have it*/ String8 subdirName(cp, nextSlash - cp); @@ -1291,7 +1291,7 @@ bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMerg dirs.add(subdirName); } - //printf("FOUND: dir '%s'\n", subdirName.string()); + //printf("FOUND: dir '%s'\n", subdirName.c_str()); } } } @@ -1425,12 +1425,12 @@ AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen) mResourceTableAsset(NULL), mResourceTable(NULL) { if (kIsDebug) { - ALOGI("Creating SharedZip %p %s\n", this, (const char*)mPath); + ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str()); } - ALOGV("+++ opening zip '%s'\n", mPath.string()); - mZipFile = ZipFileRO::open(mPath.string()); + ALOGV("+++ opening zip '%s'\n", mPath.c_str()); + mZipFile = ZipFileRO::open(mPath.c_str()); if (mZipFile == NULL) { - ALOGD("failed to open Zip archive '%s'\n", mPath.string()); + ALOGD("failed to open Zip archive '%s'\n", mPath.c_str()); } } @@ -1439,13 +1439,13 @@ AssetManager::SharedZip::SharedZip(int fd, const String8& path) mResourceTableAsset(NULL), mResourceTable(NULL) { if (kIsDebug) { - ALOGI("Creating SharedZip %p fd=%d %s\n", this, fd, (const char*)mPath); + ALOGI("Creating SharedZip %p fd=%d %s\n", this, fd, mPath.c_str()); } - ALOGV("+++ opening zip fd=%d '%s'\n", fd, mPath.string()); - mZipFile = ZipFileRO::openFd(fd, mPath.string()); + ALOGV("+++ opening zip fd=%d '%s'\n", fd, mPath.c_str()); + mZipFile = ZipFileRO::openFd(fd, mPath.c_str()); if (mZipFile == NULL) { ::close(fd); - ALOGD("failed to open Zip archive fd=%d '%s'\n", fd, mPath.string()); + ALOGD("failed to open Zip archive fd=%d '%s'\n", fd, mPath.c_str()); } } @@ -1453,7 +1453,7 @@ sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path, bool createIfNotPresent) { AutoMutex _l(gLock); - time_t modWhen = getFileModDate(path); + time_t modWhen = getFileModDate(path.c_str()); sp<SharedZip> zip = gOpen.valueFor(path).promote(); if (zip != NULL && zip->mModWhen == modWhen) { return zip; @@ -1520,7 +1520,7 @@ ResTable* AssetManager::SharedZip::setResourceTable(ResTable* res) bool AssetManager::SharedZip::isUpToDate() { - time_t modWhen = getFileModDate(mPath.string()); + time_t modWhen = getFileModDate(mPath.c_str()); return mModWhen == modWhen; } @@ -1541,7 +1541,7 @@ bool AssetManager::SharedZip::getOverlay(size_t idx, asset_path* out) const AssetManager::SharedZip::~SharedZip() { if (kIsDebug) { - ALOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath); + ALOGI("Destroying SharedZip %p %s\n", this, mPath.c_str()); } if (mResourceTable != NULL) { delete mResourceTable; @@ -1551,7 +1551,7 @@ AssetManager::SharedZip::~SharedZip() } if (mZipFile != NULL) { delete mZipFile; - ALOGV("Closed '%s'\n", mPath.string()); + ALOGV("Closed '%s'\n", mPath.c_str()); } } diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index d33b592bddc6..7f226939ffaa 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -91,12 +91,23 @@ struct FindEntryResult { StringPoolRef entry_string_ref; }; -AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) - : configuration_(configuration) { +struct Theme::Entry { + ApkAssetsCookie cookie; + uint32_t type_spec_flags; + Res_value value; +}; + +AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) { + configurations_.push_back(configuration); + // Don't invalidate caches here as there's nothing cached yet. SetApkAssets(apk_assets, false); } +AssetManager2::AssetManager2() { + configurations_.resize(1); +} + bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) { BuildDynamicRefTable(apk_assets); RebuildFilterList(); @@ -260,7 +271,7 @@ void AssetManager2::DumpToLog() const { auto op = StartOperation(); std::string list; - for (size_t i = 0; i < apk_assets_.size(); ++i) { + for (size_t i = 0, s = apk_assets_.size(); i < s; ++i) { const auto& assets = GetApkAssets(i); base::StringAppendF(&list, "%s,", assets ? assets->GetDebugName().c_str() : "nullptr"); } @@ -353,7 +364,7 @@ bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name, std::string* out) const { auto op = StartOperation(); uint8_t package_id = 0U; - for (size_t i = 0; i != apk_assets_.size(); ++i) { + for (size_t i = 0, s = apk_assets_.size(); i != s; ++i) { const auto& assets = GetApkAssets(i); if (!assets) { continue; @@ -412,7 +423,7 @@ bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name, bool AssetManager2::ContainsAllocatedTable() const { auto op = StartOperation(); - for (size_t i = 0; i != apk_assets_.size(); ++i) { + for (size_t i = 0, s = apk_assets_.size(); i != s; ++i) { const auto& assets = GetApkAssets(i); if (assets && assets->IsTableAllocated()) { return true; @@ -421,9 +432,16 @@ bool AssetManager2::ContainsAllocatedTable() const { return false; } -void AssetManager2::SetConfiguration(const ResTable_config& configuration) { - const int diff = configuration_.diff(configuration); - configuration_ = configuration; +void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) { + int diff = 0; + if (configurations_.size() != configurations.size()) { + diff = -1; + } else { + for (int i = 0; i < configurations_.size(); i++) { + diff |= configurations_[i].diff(configurations[i]); + } + } + configurations_ = std::move(configurations); if (diff) { RebuildFilterList(); @@ -620,16 +638,6 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( auto op = StartOperation(); - // Might use this if density_override != 0. - ResTable_config density_override_config; - - // Select our configuration or generate a density override configuration. - const ResTable_config* desired_config = &configuration_; - if (density_override != 0 && density_override != configuration_.density) { - density_override_config = configuration_; - density_override_config.density = density_override; - desired_config = &density_override_config; - } // Retrieve the package group from the package id of the resource id. if (UNLIKELY(!is_valid_resid(resid))) { @@ -648,119 +656,160 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( } const PackageGroup& package_group = package_groups_[package_idx]; - auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config, - stop_at_first_match, ignore_configuration); - if (UNLIKELY(!result.has_value())) { - return base::unexpected(result.error()); - } + std::optional<FindEntryResult> final_result; + bool final_has_locale = false; + bool final_overlaid = false; + for (auto & config : configurations_) { + // Might use this if density_override != 0. + ResTable_config density_override_config; + + // Select our configuration or generate a density override configuration. + const ResTable_config* desired_config = &config; + if (density_override != 0 && density_override != config.density) { + density_override_config = config; + density_override_config.density = density_override; + desired_config = &density_override_config; + } - bool overlaid = false; - if (!stop_at_first_match && !ignore_configuration) { - const auto& assets = GetApkAssets(result->cookie); - if (!assets) { - ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid); - return base::unexpected(std::nullopt); + auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config, + stop_at_first_match, ignore_configuration); + if (UNLIKELY(!result.has_value())) { + return base::unexpected(result.error()); } - if (!assets->IsLoader()) { - for (const auto& id_map : package_group.overlays_) { - auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid); - if (!overlay_entry) { - // No id map entry exists for this target resource. - continue; - } - if (overlay_entry.IsInlineValue()) { - // The target resource is overlaid by an inline value not represented by a resource. - 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; + bool overlaid = false; + if (!stop_at_first_match && !ignore_configuration) { + const auto& assets = GetApkAssets(result->cookie); + if (!assets) { + ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid); + return base::unexpected(std::nullopt); + } + if (!assets->IsLoader()) { + for (const auto& id_map : package_group.overlays_) { + auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid); + if (!overlay_entry) { + // No id map entry exists for this target resource. + continue; + } + if (overlay_entry.IsInlineValue()) { + // The target resource is overlaid by an inline value not represented by a resource. + 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; + + if (UNLIKELY(logging_enabled)) { + last_resolution_.steps.push_back(Resolution::Step{ + Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()}); + if (auto path = assets->GetPath()) { + const std::string overlay_path = path->data(); + if (IsFabricatedOverlay(overlay_path)) { + // FRRO don't have package name so we use the creating package here. + String8 frro_name = String8("FRRO"); + // Get the first part of it since the expected one should be like + // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro + // under /data/resource-cache/. + const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1); + const size_t end = name.find('-'); + if (frro_name.size() != overlay_path.size() && end != std::string::npos) { + frro_name.append(base::StringPrintf(" created by %s", + name.substr(0 /* pos */, + end).c_str()).c_str()); + } + last_resolution_.best_package_name = frro_name; + } else { + last_resolution_.best_package_name = result->package_name->c_str(); + } + } + overlaid = true; } + continue; + } + + auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override, + false /* stop_at_first_match */, + false /* ignore_configuration */); + if (UNLIKELY(IsIOError(overlay_result))) { + return base::unexpected(overlay_result.error()); + } + if (!overlay_result.has_value()) { + continue; } - if (!frro_found) { + + if (!overlay_result->config.isBetterThan(result->config, desired_config) + && overlay_result->config.compare(result->config) != 0) { + // The configuration of the entry for the overlay must be equal to or better than the + // target configuration to be chosen as the better value. continue; } - result->entry = best_frro_value; + + result->cookie = overlay_result->cookie; + result->entry = overlay_result->entry; + result->config = overlay_result->config; result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable(); - result->cookie = id_map.cookie; if (UNLIKELY(logging_enabled)) { last_resolution_.steps.push_back( - Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, String8(), result->cookie}); - if (auto path = assets->GetPath()) { - const std::string overlay_path = path->data(); - if (IsFabricatedOverlay(overlay_path)) { - // FRRO don't have package name so we use the creating package here. - String8 frro_name = String8("FRRO"); - // Get the first part of it since the expected one should be like - // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro - // under /data/resource-cache/. - const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1); - const size_t end = name.find('-'); - if (frro_name.size() != overlay_path.size() && end != std::string::npos) { - frro_name.append(base::StringPrintf(" created by %s", - name.substr(0 /* pos */, - end).c_str()).c_str()); - } - last_resolution_.best_package_name = frro_name; - } else { - last_resolution_.best_package_name = result->package_name->c_str(); - } - } + Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie, + overlay_result->config.toString()}); + last_resolution_.best_package_name = + overlay_result->package_name->c_str(); overlaid = true; } - continue; - } - - auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override, - false /* stop_at_first_match */, - false /* ignore_configuration */); - if (UNLIKELY(IsIOError(overlay_result))) { - return base::unexpected(overlay_result.error()); } - if (!overlay_result.has_value()) { - continue; - } - - if (!overlay_result->config.isBetterThan(result->config, desired_config) - && overlay_result->config.compare(result->config) != 0) { - // The configuration of the entry for the overlay must be equal to or better than the target - // configuration to be chosen as the better value. - continue; - } - - result->cookie = overlay_result->cookie; - result->entry = overlay_result->entry; - result->config = overlay_result->config; - result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable(); + } + } - if (UNLIKELY(logging_enabled)) { - last_resolution_.steps.push_back( - Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->config.toString(), - overlay_result->cookie}); - last_resolution_.best_package_name = - overlay_result->package_name->c_str(); - overlaid = true; - } + bool has_locale = false; + if (result->config.locale == 0) { + if (default_locale_ != 0) { + ResTable_config conf; + conf.locale = default_locale_; + // Since we know conf has a locale and only a locale, match will tell us if that locale + // matches + has_locale = conf.match(config); } + } else { + has_locale = true; + } + + // if we don't have a result yet + if (!final_result || + // or this config is better before the locale than the existing result + result->config.isBetterThanBeforeLocale(final_result->config, desired_config) || + // or the existing config isn't better before locale and this one specifies a locale + // whereas the existing one doesn't + (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config) + && has_locale && !final_has_locale)) { + final_result = result.value(); + final_overlaid = overlaid; + final_has_locale = has_locale; } } if (UNLIKELY(logging_enabled)) { - last_resolution_.cookie = result->cookie; - last_resolution_.type_string_ref = result->type_string_ref; - last_resolution_.entry_string_ref = result->entry_string_ref; - last_resolution_.best_config_name = result->config.toString(); - if (!overlaid) { - last_resolution_.best_package_name = result->package_name->c_str(); + last_resolution_.cookie = final_result->cookie; + last_resolution_.type_string_ref = final_result->type_string_ref; + last_resolution_.entry_string_ref = final_result->entry_string_ref; + last_resolution_.best_config_name = final_result->config.toString(); + if (!final_overlaid) { + last_resolution_.best_package_name = final_result->package_name->c_str(); } } - return result; + return *final_result; } base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( @@ -778,8 +827,10 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( // If `desired_config` is not the same as the set configuration or the caller will accept a value // from any configuration, then we cannot use our filtered list of types since it only it contains // types matched to the set configuration. - const bool use_filtered = !ignore_configuration && &desired_config == &configuration_; - + const bool use_filtered = !ignore_configuration && std::find_if( + configurations_.begin(), configurations_.end(), + [&desired_config](auto& value) { return &desired_config == &value; }) + != configurations_.end(); const size_t package_count = package_group.packages_.size(); for (size_t pi = 0; pi < package_count; pi++) { const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi]; @@ -828,8 +879,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( } else { if (UNLIKELY(logging_enabled)) { last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::SKIPPED, - this_config.toString(), - cookie}); + cookie, this_config.toString()}); } continue; } @@ -845,8 +895,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( if (!offset.has_value()) { if (UNLIKELY(logging_enabled)) { last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY, - this_config.toString(), - cookie}); + cookie, this_config.toString()}); } continue; } @@ -859,8 +908,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( if (UNLIKELY(logging_enabled)) { last_resolution_.steps.push_back(Resolution::Step{resolution_type, - this_config.toString(), - cookie}); + cookie, this_config.toString()}); } // Any configuration will suffice, so break. @@ -889,11 +937,11 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( .entry = *entry, .config = *best_config, .type_flags = type_flags, + .dynamic_ref_table = package_group.dynamic_ref_table.get(), .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_verified)->key()), - .dynamic_ref_table = package_group.dynamic_ref_table.get(), }; } @@ -937,35 +985,48 @@ std::string AssetManager2::GetLastResourceResolution() const { } std::stringstream log_stream; - log_stream << base::StringPrintf("Resolution for 0x%08x %s\n" - "\tFor config - %s", resid, resource_name_string.c_str(), - configuration_.toString().c_str()); - + if (configurations_.size() == 1) { + log_stream << base::StringPrintf("Resolution for 0x%08x %s\n" + "\tFor config - %s", resid, resource_name_string.c_str(), + configurations_[0].toString().c_str()); + } else { + ResTable_config conf = configurations_[0]; + conf.clearLocale(); + log_stream << base::StringPrintf("Resolution for 0x%08x %s\n\tFor config - %s and locales", + resid, resource_name_string.c_str(), conf.toString().c_str()); + char str[40]; + str[0] = '\0'; + for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) { + iter->getBcp47Locale(str); + log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : ""); + } + } for (const Resolution::Step& step : last_resolution_.steps) { - const static std::unordered_map<Resolution::Step::Type, const char*> kStepStrings = { - {Resolution::Step::Type::INITIAL, "Found initial"}, - {Resolution::Step::Type::BETTER_MATCH, "Found better"}, - {Resolution::Step::Type::OVERLAID, "Overlaid"}, - {Resolution::Step::Type::OVERLAID_INLINE, "Overlaid inline"}, - {Resolution::Step::Type::SKIPPED, "Skipped"}, - {Resolution::Step::Type::NO_ENTRY, "No entry"} + constexpr static std::array kStepStrings = { + "Found initial", + "Found better", + "Overlaid", + "Overlaid inline", + "Skipped", + "No entry" }; - const auto prefix = kStepStrings.find(step.type); - if (prefix == kStepStrings.end()) { + if (step.type < Resolution::Step::Type::INITIAL + || step.type > Resolution::Step::Type::NO_ENTRY) { continue; } + const auto prefix = kStepStrings[int(step.type) - int(Resolution::Step::Type::INITIAL)]; const auto& assets = GetApkAssets(step.cookie); - log_stream << "\n\t" << prefix->second << ": " - << (assets ? assets->GetDebugName() : "<null>") << " #" << step.cookie; - if (!step.config_name.isEmpty()) { + log_stream << "\n\t" << prefix << ": " << (assets ? assets->GetDebugName() : "<null>") + << " #" << step.cookie; + if (!step.config_name.empty()) { log_stream << " - " << step.config_name; } } log_stream << "\nBest matching is from " - << (last_resolution_.best_config_name.isEmpty() ? "default" - : last_resolution_.best_config_name) + << (last_resolution_.best_config_name.empty() ? "default" + : last_resolution_.best_config_name.c_str()) << " configuration of " << last_resolution_.best_package_name; return log_stream.str(); } @@ -1094,16 +1155,19 @@ base::expected<std::monostate, NullOrIOError> AssetManager2::ResolveReference( } } -const std::vector<uint32_t> AssetManager2::GetBagResIdStack(uint32_t resid) const { - auto cached_iter = cached_bag_resid_stacks_.find(resid); - if (cached_iter != cached_bag_resid_stacks_.end()) { - return cached_iter->second; +base::expected<const std::vector<uint32_t>*, NullOrIOError> AssetManager2::GetBagResIdStack( + uint32_t resid) const { + auto it = cached_bag_resid_stacks_.find(resid); + if (it != cached_bag_resid_stacks_.end()) { + return &it->second; + } + std::vector<uint32_t> stacks; + if (auto maybe_bag = GetBag(resid, stacks); UNLIKELY(IsIOError(maybe_bag))) { + return base::unexpected(maybe_bag.error()); } - std::vector<uint32_t> found_resids; - GetBag(resid, found_resids); - cached_bag_resid_stacks_.emplace(resid, found_resids); - return found_resids; + it = cached_bag_resid_stacks_.emplace(resid, std::move(stacks)).first; + return &it->second; } base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::ResolveBag( @@ -1120,9 +1184,15 @@ 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, std::move(found_resids)); + auto resid_stacks_it = cached_bag_resid_stacks_.find(resid); + if (resid_stacks_it == cached_bag_resid_stacks_.end()) { + resid_stacks_it = cached_bag_resid_stacks_.emplace(resid, std::vector<uint32_t>{}).first; + } + const auto bag = GetBag(resid, resid_stacks_it->second); + if (UNLIKELY(IsIOError(bag))) { + cached_bag_resid_stacks_.erase(resid_stacks_it); + return base::unexpected(bag.error()); + } return bag; } @@ -1420,11 +1490,14 @@ void AssetManager2::RebuildFilterList() { 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_)) { - if (!group) { - group = &package.filtered_configs_.editItemAt(type_id - 1); + for (auto & config : configurations_) { + if (type_entry.config.match(config)) { + if (!group) { + group = &package.filtered_configs_.editItemAt(type_id - 1); + } + group->type_entries.push_back(&type_entry); + break; } - group->type_entries.push_back(&type_entry); } } }); @@ -1435,25 +1508,40 @@ void AssetManager2::RebuildFilterList() { } void AssetManager2::InvalidateCaches(uint32_t diff) { - cached_bag_resid_stacks_.clear(); + cached_resolved_values_.clear(); if (diff == 0xffffffffu) { // Everything must go. cached_bags_.clear(); + cached_bag_resid_stacks_.clear(); return; } // Be more conservative with what gets purged. Only if the bag has other possible // variations with respect to what changed (diff) should we remove it. - for (auto iter = cached_bags_.cbegin(); iter != cached_bags_.cend();) { - if (diff & iter->second->type_spec_flags) { - iter = cached_bags_.erase(iter); + for (auto stack_it = cached_bag_resid_stacks_.begin(); + stack_it != cached_bag_resid_stacks_.end();) { + const auto it = cached_bags_.find(stack_it->first); + if (it == cached_bags_.end()) { + stack_it = cached_bag_resid_stacks_.erase(stack_it); + } else if ((diff & it->second->type_spec_flags) != 0) { + cached_bags_.erase(it); + stack_it = cached_bag_resid_stacks_.erase(stack_it); } else { - ++iter; + ++stack_it; // Keep the item in both caches. } } - cached_resolved_values_.clear(); + // Need to ensure that both bag caches are consistent, as we populate them in the same function. + // Iterate over the cached bags to erase the items without the corresponding resid_stack cache + // items. + for (auto it = cached_bags_.begin(); it != cached_bags_.end();) { + if ((diff & it->second->type_spec_flags) != 0) { + it = cached_bags_.erase(it); + } else { + ++it; + } + } } uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const { @@ -1523,12 +1611,6 @@ Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) { Theme::~Theme() = default; -struct Theme::Entry { - ApkAssetsCookie cookie; - uint32_t type_spec_flags; - Res_value value; -}; - base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) { ATRACE_NAME("Theme::ApplyStyle"); diff --git a/libs/androidfw/BackupData.cpp b/libs/androidfw/BackupData.cpp index 76a430ee9f0e..fec0e772674d 100644 --- a/libs/androidfw/BackupData.cpp +++ b/libs/androidfw/BackupData.cpp @@ -106,8 +106,8 @@ BackupDataWriter::WriteEntityHeader(const String8& key, size_t dataSize) k = key; } if (kIsDebug) { - ALOGD("Writing header: prefix='%s' key='%s' dataSize=%zu", m_keyPrefix.string(), - key.string(), dataSize); + ALOGD("Writing header: prefix='%s' key='%s' dataSize=%zu", m_keyPrefix.c_str(), + key.c_str(), dataSize); } entity_header_v1 header; @@ -128,7 +128,7 @@ BackupDataWriter::WriteEntityHeader(const String8& key, size_t dataSize) m_pos += amt; if (kIsDebug) ALOGI("writing entity header key, %zd bytes", keyLen+1); - amt = write(m_fd, k.string(), keyLen+1); + amt = write(m_fd, k.c_str(), keyLen+1); if (amt != keyLen+1) { m_status = errno; return m_status; diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp index e80e9486c8b2..1a6a952492f6 100644 --- a/libs/androidfw/BackupHelpers.cpp +++ b/libs/androidfw/BackupHelpers.cpp @@ -30,6 +30,7 @@ #include <utime.h> #include <zlib.h> +#include <androidfw/PathUtils.h> #include <log/log.h> #include <utils/ByteOrder.h> #include <utils/KeyedVector.h> @@ -179,7 +180,7 @@ write_snapshot_file(int fd, const KeyedVector<String8,FileRec>& snapshot) } // filename is not NULL terminated, but it is padded - amt = write(fd, name.string(), nameLen); + amt = write(fd, name.c_str(), nameLen); if (amt != nameLen) { ALOGW("write_snapshot_file error writing filename %s", strerror(errno)); return 1; @@ -203,7 +204,7 @@ write_snapshot_file(int fd, const KeyedVector<String8,FileRec>& snapshot) static int write_delete_file(BackupDataWriter* dataStream, const String8& key) { - LOGP("write_delete_file %s\n", key.string()); + LOGP("write_delete_file %s\n", key.c_str()); return dataStream->WriteEntityHeader(key, -1); } @@ -211,7 +212,7 @@ static int write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& key, char const* realFilename) { - LOGP("write_update_file %s (%s) : mode 0%o\n", realFilename, key.string(), mode); + LOGP("write_update_file %s (%s) : mode 0%o\n", realFilename, key.c_str(), mode); const int bufsize = 4*1024; int err; @@ -365,7 +366,7 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD r.s.size = st.st_size; if (newSnapshot.indexOfKey(key) >= 0) { - LOGP("back_up_files key already in use '%s'", key.string()); + LOGP("back_up_files key already in use '%s'", key.c_str()); return -1; } @@ -390,30 +391,30 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD int cmp = p.compare(q); if (cmp < 0) { // file present in oldSnapshot, but not present in newSnapshot - LOGP("file removed: %s", p.string()); + LOGP("file removed: %s", p.c_str()); write_delete_file(dataStream, p); n++; } else if (cmp > 0) { // file added - LOGP("file added: %s crc=0x%08x", g.file.string(), g.s.crc32); - write_update_file(dataStream, q, g.file.string()); + LOGP("file added: %s crc=0x%08x", g.file.c_str(), g.s.crc32); + write_update_file(dataStream, q, g.file.c_str()); m++; } else { // same file exists in both old and new; check whether to update const FileState& f = oldSnapshot.valueAt(n); - LOGP("%s", q.string()); + LOGP("%s", q.c_str()); LOGP(" old: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x", f.modTime_sec, f.modTime_nsec, f.mode, f.size, f.crc32); LOGP(" new: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x", g.s.modTime_sec, g.s.modTime_nsec, g.s.mode, g.s.size, g.s.crc32); if (f.modTime_sec != g.s.modTime_sec || f.modTime_nsec != g.s.modTime_nsec || f.mode != g.s.mode || f.size != g.s.size || f.crc32 != g.s.crc32) { - int fd = open(g.file.string(), O_RDONLY); + int fd = open(g.file.c_str(), O_RDONLY); if (fd < 0) { - ALOGE("Unable to read file for backup: %s", g.file.string()); + ALOGE("Unable to read file for backup: %s", g.file.c_str()); } else { - write_update_file(dataStream, fd, g.s.mode, p, g.file.string()); + write_update_file(dataStream, fd, g.s.mode, p, g.file.c_str()); close(fd); } } @@ -432,7 +433,7 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD while (m<M) { const String8& q = newSnapshot.keyAt(m); FileRec& g = newSnapshot.editValueAt(m); - write_update_file(dataStream, q, g.file.string()); + write_update_file(dataStream, q, g.file.c_str()); m++; } @@ -483,7 +484,7 @@ int write_tarfile(const String8& packageName, const String8& domain, BackupDataWriter* writer) { // In the output stream everything is stored relative to the root - const char* relstart = filepath.string() + rootpath.length(); + const char* relstart = filepath.c_str() + rootpath.length(); if (*relstart == '/') relstart++; // won't be true when path == rootpath String8 relpath(relstart); @@ -514,9 +515,9 @@ int write_tarfile(const String8& packageName, const String8& domain, int err = 0; struct stat64 s; - if (lstat64(filepath.string(), &s) != 0) { + if (lstat64(filepath.c_str(), &s) != 0) { err = errno; - ALOGE("Error %d (%s) from lstat64(%s)", err, strerror(err), filepath.string()); + ALOGE("Error %d (%s) from lstat64(%s)", err, strerror(err), filepath.c_str()); return err; } @@ -541,10 +542,10 @@ int write_tarfile(const String8& packageName, const String8& domain, // !!! TODO: use mmap when possible to avoid churning the buffer cache // !!! TODO: this will break with symlinks; need to use readlink(2) - int fd = open(filepath.string(), O_RDONLY); + int fd = open(filepath.c_str(), O_RDONLY); if (fd < 0) { err = errno; - ALOGE("Error %d (%s) from open(%s)", err, strerror(err), filepath.string()); + ALOGE("Error %d (%s) from open(%s)", err, strerror(err), filepath.c_str()); return err; } @@ -592,7 +593,7 @@ int write_tarfile(const String8& packageName, const String8& domain, } else if (S_ISREG(s.st_mode)) { type = '0'; // tar magic: '0' == normal file } else { - ALOGW("Error: unknown file mode 0%o [%s]", s.st_mode, filepath.string()); + ALOGW("Error: unknown file mode 0%o [%s]", s.st_mode, filepath.c_str()); goto cleanup; } buf[156] = type; @@ -606,30 +607,30 @@ int write_tarfile(const String8& packageName, const String8& domain, prefix += packageName; } if (domain.length() > 0) { - prefix.appendPath(domain); + appendPath(prefix, domain); } // pax extended means we don't put in a prefix field, and put a different // string in the basic name field. We can also construct the full path name // out of the substrings we've now built. fullname = prefix; - fullname.appendPath(relpath); + appendPath(fullname, relpath); // ustar: // [ 0 : 100 ]; file name/path // [ 345 : 155 ] filename path prefix // We only use the prefix area if fullname won't fit in the path if (fullname.length() > 100) { - strncpy(buf, relpath.string(), 100); - strncpy(buf + 345, prefix.string(), 155); + strncpy(buf, relpath.c_str(), 100); + strncpy(buf + 345, prefix.c_str(), 155); } else { - strncpy(buf, fullname.string(), 100); + strncpy(buf, fullname.c_str(), 100); } } // [ 329 : 8 ] and [ 337 : 8 ] devmajor/devminor, not used - ALOGI(" Name: %s", fullname.string()); + ALOGI(" Name: %s", fullname.c_str()); // If we're using a pax extended header, build & write that here; lengths are // already preflighted @@ -647,18 +648,18 @@ int write_tarfile(const String8& packageName, const String8& domain, // fullname was generated above with the ustar paths paxLen += write_pax_header_entry(paxData + paxLen, PAXDATA_SIZE - paxLen, - "path", fullname.string()); + "path", fullname.c_str()); // Now we know how big the pax data is // Now build the pax *header* templated on the ustar header memcpy(paxHeader, buf, 512); - String8 leaf = fullname.getPathLeaf(); + String8 leaf = getPathLeaf(fullname); memset(paxHeader, 0, 100); // rewrite the name area - snprintf(paxHeader, 100, "PaxHeader/%s", leaf.string()); + snprintf(paxHeader, 100, "PaxHeader/%s", leaf.c_str()); memset(paxHeader + 345, 0, 155); // rewrite the prefix area - strncpy(paxHeader + 345, prefix.string(), 155); + strncpy(paxHeader + 345, prefix.c_str(), 155); paxHeader[156] = 'x'; // mark it as a pax extended header @@ -691,12 +692,12 @@ int write_tarfile(const String8& packageName, const String8& domain, ssize_t nRead = read(fd, buf, toRead); if (nRead < 0) { err = errno; - ALOGE("Unable to read file [%s], err=%d (%s)", filepath.string(), + ALOGE("Unable to read file [%s], err=%d (%s)", filepath.c_str(), err, strerror(err)); break; } else if (nRead == 0) { ALOGE("EOF but expect %lld more bytes in [%s]", (long long) toWrite, - filepath.string()); + filepath.c_str()); err = EIO; break; } @@ -762,7 +763,7 @@ RestoreHelperBase::WriteFile(const String8& filename, BackupDataReader* in) file_metadata_v1 metadata; amt = in->ReadEntityData(&metadata, sizeof(metadata)); if (amt != sizeof(metadata)) { - ALOGW("Could not read metadata for %s -- %ld / %s", filename.string(), + ALOGW("Could not read metadata for %s -- %ld / %s", filename.c_str(), (long)amt, strerror(errno)); return EIO; } @@ -779,9 +780,9 @@ RestoreHelperBase::WriteFile(const String8& filename, BackupDataReader* in) // Write the file and compute the crc crc = crc32(0L, Z_NULL, 0); - fd = open(filename.string(), O_CREAT|O_RDWR|O_TRUNC, mode); + fd = open(filename.c_str(), O_CREAT|O_RDWR|O_TRUNC, mode); if (fd == -1) { - ALOGW("Could not open file %s -- %s", filename.string(), strerror(errno)); + ALOGW("Could not open file %s -- %s", filename.c_str(), strerror(errno)); return errno; } @@ -789,7 +790,7 @@ RestoreHelperBase::WriteFile(const String8& filename, BackupDataReader* in) err = write(fd, buf, amt); if (err != amt) { close(fd); - ALOGW("Error '%s' writing '%s'", strerror(errno), filename.string()); + ALOGW("Error '%s' writing '%s'", strerror(errno), filename.c_str()); return errno; } crc = crc32(crc, (Bytef*)buf, amt); @@ -798,9 +799,9 @@ RestoreHelperBase::WriteFile(const String8& filename, BackupDataReader* in) close(fd); // Record for the snapshot - err = stat(filename.string(), &st); + err = stat(filename.c_str(), &st); if (err != 0) { - ALOGW("Error stating file that we just created %s", filename.string()); + ALOGW("Error stating file that we just created %s", filename.c_str()); return errno; } @@ -1104,9 +1105,9 @@ backup_helper_test_four() fprintf(stderr, "state %zu expected={%d/%d, %04o, 0x%08x, 0x%08x, %3zu} '%s'\n" " actual={%d/%d, %04o, 0x%08x, 0x%08x, %3d} '%s'\n", i, states[i].modTime_sec, states[i].modTime_nsec, states[i].mode, states[i].size, - states[i].crc32, name.length(), filenames[i].string(), + states[i].crc32, name.length(), filenames[i].c_str(), state.modTime_sec, state.modTime_nsec, state.mode, state.size, state.crc32, - state.nameLen, name.string()); + state.nameLen, name.c_str()); matched = false; } } @@ -1152,9 +1153,9 @@ test_write_header_and_entity(BackupDataWriter& writer, const char* str) return err; } - err = writer.WriteEntityData(text.string(), text.length()+1); + err = writer.WriteEntityData(text.c_str(), text.length()+1); if (err != 0) { - fprintf(stderr, "write failed for data '%s'\n", text.string()); + fprintf(stderr, "write failed for data '%s'\n", text.c_str()); return errno; } @@ -1230,7 +1231,7 @@ test_read_header_and_entity(BackupDataReader& reader, const char* str) goto finished; } if (string != str) { - fprintf(stderr, "ReadEntityHeader expected key '%s' got '%s'\n", str, string.string()); + fprintf(stderr, "ReadEntityHeader expected key '%s' got '%s'\n", str, string.c_str()); err = EINVAL; goto finished; } diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp index cf2fd6f59b87..e08030c4cca5 100644 --- a/libs/androidfw/ConfigDescription.cpp +++ b/libs/androidfw/ConfigDescription.cpp @@ -905,7 +905,7 @@ std::string ConfigDescription::GetBcp47LanguageTag(bool canonicalize) const { std::string ConfigDescription::to_string() const { const String8 str = toString(); - return std::string(str.string(), str.size()); + return std::string(str.c_str(), str.size()); } bool ConfigDescription::Dominates(const ConfigDescription& o) const { diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index 2a6dc7b95c07..5e645cceea2d 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -84,7 +84,7 @@ status_t CursorWindow::maybeInflate() { String8 ashmemName("CursorWindow: "); ashmemName.append(mName); - ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize); + ashmemFd = ashmem_create_region(ashmemName.c_str(), mInflatedSize); if (ashmemFd < 0) { PLOG(ERROR) << "Failed ashmem_create_region"; goto fail_silent; diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index 89835742c8ff..5f98b8f8db43 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -294,14 +294,14 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie dtohl(header->version), kIdmapCurrentVersion); return {}; } + std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path"); + if (!target_path) { + return {}; + } std::optional<std::string_view> overlay_path = ReadString(&data_ptr, &data_size, "overlay path"); if (!overlay_path) { return {}; } - std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path"); - if (!target_path) { - return {}; - } if (!ReadString(&data_ptr, &data_size, "target name") || !ReadString(&data_ptr, &data_size, "debug info")) { return {}; @@ -364,7 +364,7 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie return std::unique_ptr<LoadedIdmap>( 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)); + overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path)); } bool LoadedIdmap::IsUpToDate() const { diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index fbfae5e2bcbe..c9d5e074271b 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -494,6 +494,8 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, util::ReadUtf16StringFromDevice(header->name, arraysize(header->name), &loaded_package->package_name_); + const bool only_overlayable = (property_flags & PROPERTY_ONLY_OVERLAYABLES) != 0; + // A map of TypeSpec builders, each associated with an type index. // We use these to accumulate the set of Types available for a TypeSpec, and later build a single, // contiguous block of memory that holds all the Types together with the TypeSpec. @@ -502,6 +504,9 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, ChunkIterator iter(chunk.data_ptr(), chunk.data_size()); while (iter.HasNext()) { const Chunk child_chunk = iter.Next(); + if (only_overlayable && child_chunk.type() != RES_TABLE_OVERLAYABLE_TYPE) { + continue; + } switch (child_chunk.type()) { case RES_STRING_POOL_TYPE: { const auto pool_address = child_chunk.header<ResChunk_header>(); @@ -655,6 +660,9 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, << name_to_actor_it->first << "'."; return {}; } + if (only_overlayable) { + break; + } // Iterate over the overlayable policy chunks contained within the overlayable chunk data ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size()); @@ -800,14 +808,21 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, global_string_pool_ = util::make_unique<OverlayStringPool>(loaded_idmap); } + const bool only_overlayable = (property_flags & PROPERTY_ONLY_OVERLAYABLES) != 0; + const size_t package_count = dtohl(header->packageCount); size_t packages_seen = 0; - packages_.reserve(package_count); + if (!only_overlayable) { + packages_.reserve(package_count); + } ChunkIterator iter(chunk.data_ptr(), chunk.data_size()); while (iter.HasNext()) { const Chunk child_chunk = iter.Next(); + if (only_overlayable && child_chunk.type() != RES_TABLE_PACKAGE_TYPE) { + continue; + } switch (child_chunk.type()) { case RES_STRING_POOL_TYPE: // Only use the first string pool. Ignore others. @@ -837,6 +852,10 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, return false; } packages_.push_back(std::move(loaded_package)); + if (only_overlayable) { + // Overlayable is always in the first package, no need to process anything else. + return true; + } } break; default: diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS index 17f5164cf417..ef4cc46cb1c8 100644 --- a/libs/androidfw/OWNERS +++ b/libs/androidfw/OWNERS @@ -1,7 +1,6 @@ set noparent -toddke@google.com zyy@google.com patb@google.com per-file CursorWindow.cpp=omakoto@google.com -per-file LocaleDataTables.cpp=vichang@google.com,ngeoffray@google.com,nikitai@google.com +per-file LocaleDataTables.cpp=vichang@google.com,ngeoffray@google.com diff --git a/libs/androidfw/ObbFile.cpp b/libs/androidfw/ObbFile.cpp index 95332a35eb7d..c6a9632ee7b7 100644 --- a/libs/androidfw/ObbFile.cpp +++ b/libs/androidfw/ObbFile.cpp @@ -217,7 +217,7 @@ bool ObbFile::parseObbFile(int fd) free(scanBuf); #ifdef DEBUG - ALOGI("Obb scan succeeded: packageName=%s, version=%d\n", mPackageName.string(), mVersion); + ALOGI("Obb scan succeeded: packageName=%s, version=%d\n", mPackageName.c_str(), mVersion); #endif return true; @@ -288,7 +288,7 @@ bool ObbFile::writeTo(int fd) return false; } - if (write(fd, mPackageName.string(), packageNameLen) != (ssize_t)packageNameLen) { + if (write(fd, mPackageName.c_str(), packageNameLen) != (ssize_t)packageNameLen) { ALOGW("couldn't write package name: %s\n", strerror(errno)); return false; } diff --git a/libs/androidfw/PathUtils.cpp b/libs/androidfw/PathUtils.cpp new file mode 100644 index 000000000000..df7a9f06781b --- /dev/null +++ b/libs/androidfw/PathUtils.cpp @@ -0,0 +1,135 @@ +/* + * 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. + */ + +#include <androidfw/PathUtils.h> + +#include <utils/Compat.h> + +namespace android { + +String8 getPathLeaf(const String8& str) { + const char* cp; + const char*const buf = str.c_str(); + + cp = strrchr(buf, OS_PATH_SEPARATOR); + if (cp == nullptr) + return str; + else + return String8(cp+1); +} + +String8 getPathDir(const String8& str8) { + const char* cp; + const char*const str = str8.c_str(); + + cp = strrchr(str, OS_PATH_SEPARATOR); + if (cp == nullptr) + return String8(); + else + return String8(str, cp - str); +} + +static char* findExtension(const String8& str8) { + const char* lastSlash; + const char* lastDot; + const char* const str = str8.c_str(); + + // only look at the filename + lastSlash = strrchr(str, OS_PATH_SEPARATOR); + if (lastSlash == nullptr) + lastSlash = str; + else + lastSlash++; + + // find the last dot + lastDot = strrchr(lastSlash, '.'); + if (lastDot == nullptr) + return nullptr; + + // looks good, ship it + return const_cast<char*>(lastDot); +} + +String8 getPathExtension(const String8& str) { + char* ext; + + ext = findExtension(str); + if (ext != nullptr) + return String8(ext); + else + return String8(); +} + +String8 getBasePath(const String8& str8) { + char* ext; + const char* const str = str8.c_str(); + + ext = findExtension(str8); + if (ext == nullptr) + return str8; + else + return String8(str, ext - str); +} + +static void setPathName(String8& s, const char* name) { + size_t len = strlen(name); + char* buf = s.lockBuffer(len); + + memcpy(buf, name, len); + + // remove trailing path separator, if present + if (len > 0 && buf[len - 1] == OS_PATH_SEPARATOR) len--; + buf[len] = '\0'; + + s.unlockBuffer(len); +} + +String8& appendPath(String8& str, const char* name) { + // TODO: The test below will fail for Win32 paths. Fix later or ignore. + if (name[0] != OS_PATH_SEPARATOR) { + if (*name == '\0') { + // nothing to do + return str; + } + + size_t len = str.length(); + if (len == 0) { + // no existing filename, just use the new one + setPathName(str, name); + return str; + } + + // make room for oldPath + '/' + newPath + int newlen = strlen(name); + + char* buf = str.lockBuffer(len+1+newlen); + + // insert a '/' if needed + if (buf[len-1] != OS_PATH_SEPARATOR) + buf[len++] = OS_PATH_SEPARATOR; + + memcpy(buf+len, name, newlen+1); + len += newlen; + + str.unlockBuffer(len); + return str; + } else { + setPathName(str, name); + return str; + } +} + +} // namespace android diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 29d33da6b2f7..4c992becda7c 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -1042,7 +1042,7 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0) { if (kDebugStringPoolNoisy) { - ALOGI("indexOfString UTF-8: %s", String8(str, strLen).string()); + ALOGI("indexOfString UTF-8: %s", String8(str, strLen).c_str()); } // The string pool contains UTF 8 strings; we don't want to cause @@ -1103,7 +1103,7 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ ALOGI("Looking at %s, i=%d\n", s->data(), i); } if (str8Len == s->size() - && memcmp(s->data(), str8.string(), str8Len) == 0) { + && memcmp(s->data(), str8.c_str(), str8Len) == 0) { if (kDebugStringPoolNoisy) { ALOGI("MATCH!"); } @@ -1115,7 +1115,7 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ } else { if (kDebugStringPoolNoisy) { - ALOGI("indexOfString UTF-16: %s", String8(str, strLen).string()); + ALOGI("indexOfString UTF-16: %s", String8(str, strLen).c_str()); } if (mHeader->flags&ResStringPool_header::SORTED_FLAG) { @@ -1133,7 +1133,7 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ int c = s.has_value() ? strzcmp16(s->data(), s->size(), str, strLen) : -1; if (kDebugStringPoolNoisy) { ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n", - String8(s->data(), s->size()).string(), c, (int)l, (int)mid, (int)h); + String8(s->data(), s->size()).c_str(), c, (int)l, (int)mid, (int)h); } if (c == 0) { if (kDebugStringPoolNoisy) { @@ -1157,7 +1157,7 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ return base::unexpected(s.error()); } if (kDebugStringPoolNoisy) { - ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).string(), i); + ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i); } if (s.has_value() && strLen == s->size() && strzcmp16(s->data(), s->size(), str, strLen) == 0) { @@ -1525,8 +1525,8 @@ ssize_t ResXMLParser::indexOfAttribute(const char* ns, const char* attr) const { String16 nsStr(ns != NULL ? ns : ""); String16 attrStr(attr); - return indexOfAttribute(ns ? nsStr.string() : NULL, ns ? nsStr.size() : 0, - attrStr.string(), attrStr.size()); + return indexOfAttribute(ns ? nsStr.c_str() : NULL, ns ? nsStr.size() : 0, + attrStr.c_str(), attrStr.size()); } ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen, @@ -1544,8 +1544,8 @@ ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen, } attr8 = String8(attr, attrLen); if (kDebugStringPoolNoisy) { - ALOGI("indexOfAttribute UTF8 %s (%zu) / %s (%zu)", ns8.string(), nsLen, - attr8.string(), attrLen); + ALOGI("indexOfAttribute UTF8 %s (%zu) / %s (%zu)", ns8.c_str(), nsLen, + attr8.c_str(), attrLen); } for (size_t i=0; i<N; i++) { size_t curNsLen = 0, curAttrLen = 0; @@ -1555,7 +1555,7 @@ ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen, ALOGI(" curNs=%s (%zu), curAttr=%s (%zu)", curNs, curNsLen, curAttr, curAttrLen); } if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen - && memcmp(attr8.string(), curAttr, attrLen) == 0) { + && memcmp(attr8.c_str(), curAttr, attrLen) == 0) { if (ns == NULL) { if (curNs == NULL) { if (kDebugStringPoolNoisy) { @@ -1565,8 +1565,8 @@ ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen, } } else if (curNs != NULL) { //printf(" --> ns=%s, curNs=%s\n", - // String8(ns).string(), String8(curNs).string()); - if (memcmp(ns8.string(), curNs, nsLen) == 0) { + // String8(ns).c_str(), String8(curNs).c_str()); + if (memcmp(ns8.c_str(), curNs, nsLen) == 0) { if (kDebugStringPoolNoisy) { ALOGI(" FOUND!"); } @@ -1578,8 +1578,8 @@ ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen, } else { if (kDebugStringPoolNoisy) { ALOGI("indexOfAttribute UTF16 %s (%zu) / %s (%zu)", - String8(ns, nsLen).string(), nsLen, - String8(attr, attrLen).string(), attrLen); + String8(ns, nsLen).c_str(), nsLen, + String8(attr, attrLen).c_str(), attrLen); } for (size_t i=0; i<N; i++) { size_t curNsLen = 0, curAttrLen = 0; @@ -1587,8 +1587,8 @@ ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen, const char16_t* curAttr = getAttributeName(i, &curAttrLen); if (kDebugStringPoolNoisy) { ALOGI(" curNs=%s (%zu), curAttr=%s (%zu)", - String8(curNs, curNsLen).string(), curNsLen, - String8(curAttr, curAttrLen).string(), curAttrLen); + String8(curNs, curNsLen).c_str(), curNsLen, + String8(curAttr, curAttrLen).c_str(), curAttrLen); } if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen && (memcmp(attr, curAttr, attrLen*sizeof(char16_t)) == 0)) { @@ -1601,7 +1601,7 @@ ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen, } } else if (curNs != NULL) { //printf(" --> ns=%s, curNs=%s\n", - // String8(ns).string(), String8(curNs).string()); + // String8(ns).c_str(), String8(curNs).c_str()); if (memcmp(ns, curNs, nsLen*sizeof(char16_t)) == 0) { if (kDebugStringPoolNoisy) { ALOGI(" FOUND!"); @@ -1769,13 +1769,21 @@ ResXMLTree::~ResXMLTree() status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData) { + const ResChunk_header* chunk = nullptr; + const ResChunk_header* lastChunk = nullptr; + uninit(); mEventCode = START_DOCUMENT; if (!data || !size) { return (mError=BAD_TYPE); } - + if (size < sizeof(ResXMLTree_header)) { + ALOGW("Bad XML block: total size %d is less than the header size %d\n", + int(size), int(sizeof(ResXMLTree_header))); + mError = BAD_TYPE; + goto done; + } if (copyData) { mOwnedData = malloc(size); if (mOwnedData == NULL) { @@ -1792,9 +1800,15 @@ status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData) (int)dtohs(mHeader->header.headerSize), (int)dtohl(mHeader->header.size), (int)size); mError = BAD_TYPE; - restart(); - return mError; + goto done; } + if (dtohs(mHeader->header.type) != RES_XML_TYPE) { + ALOGW("Bad XML block: expected root block type %d, got %d\n", + int(RES_XML_TYPE), int(dtohs(mHeader->header.type))); + mError = BAD_TYPE; + goto done; + } + mDataEnd = ((const uint8_t*)mHeader) + mSize; mStrings.uninit(); @@ -1804,9 +1818,8 @@ status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData) // First look for a couple interesting chunks: the string block // and first XML node. - const ResChunk_header* chunk = - (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize)); - const ResChunk_header* lastChunk = chunk; + chunk = (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize)); + lastChunk = chunk; while (((const uint8_t*)chunk) < (mDataEnd-sizeof(ResChunk_header)) && ((const uint8_t*)chunk) < (mDataEnd-dtohl(chunk->size))) { status_t err = validate_chunk(chunk, sizeof(ResChunk_header), mDataEnd, "XML"); @@ -1860,7 +1873,11 @@ status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData) mError = mStrings.getError(); done: - restart(); + if (mError) { + uninit(); + } else { + restart(); + } return mError; } @@ -2551,6 +2568,22 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, return false; } +bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o, + const ResTable_config* requested) const { + if (requested) { + if (imsi || o.imsi) { + if ((mcc != o.mcc) && requested->mcc) { + return (mcc); + } + + if ((mnc != o.mnc) && requested->mnc) { + return (mnc); + } + } + } + return false; +} + bool ResTable_config::isBetterThan(const ResTable_config& o, const ResTable_config* requested) const { if (requested) { @@ -4458,7 +4491,7 @@ bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* ou return false; } - outName->package = grp->name.string(); + outName->package = grp->name.c_str(); outName->packageLen = grp->name.size(); if (allowUtf8) { outName->type8 = UnpackOptionalString(entry.typeStr.string8(), &outName->typeLen); @@ -4558,7 +4591,7 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag outValue->dataType, outValue->dataType == Res_value::TYPE_STRING ? String8(UnpackOptionalString( - entry.package->header->values.stringAt(outValue->data), &len)).string() : + entry.package->header->values.stringAt(outValue->data), &len)).c_str() : "", outValue->data); } @@ -4927,7 +4960,7 @@ void ResTable::setParameters(const ResTable_config* params) AutoMutex _lock2(mFilteredConfigLock); if (kDebugTableGetEntry) { - ALOGI("Setting parameters: %s\n", params->toString().string()); + ALOGI("Setting parameters: %s\n", params->toString().c_str()); } mParams = *params; for (size_t p = 0; p < mPackageGroups.size(); p++) { @@ -5038,7 +5071,7 @@ nope: if (name[1] == 'i' && name[2] == 'n' && name[3] == 'd' && name[4] == 'e' && name[5] == 'x' && name[6] == '_') { - int index = atoi(String8(name + 7, nameLen - 7).string()); + int index = atoi(String8(name + 7, nameLen - 7).c_str()); if (Res_CHECKID(index)) { ALOGW("Array resource index: %d is too large.", index); @@ -5104,9 +5137,9 @@ nope: if (kDebugTableNoisy) { printf("Looking for identifier: type=%s, name=%s, package=%s\n", - String8(type, typeLen).string(), - String8(name, nameLen).string(), - String8(package, packageLen).string()); + String8(type, typeLen).c_str(), + String8(name, nameLen).c_str(), + String8(package, packageLen).c_str()); } const String16 attr("attr"); @@ -5117,9 +5150,9 @@ nope: const PackageGroup* group = mPackageGroups[ig]; if (strzcmp16(package, packageLen, - group->name.string(), group->name.size())) { + group->name.c_str(), group->name.size())) { if (kDebugTableNoisy) { - printf("Skipping package group: %s\n", String8(group->name).string()); + printf("Skipping package group: %s\n", String8(group->name).c_str()); } continue; } @@ -5144,8 +5177,8 @@ nope: } return identifier; } - } while (strzcmp16(attr.string(), attr.size(), targetType, targetTypeLen) == 0 - && (targetType = attrPrivate.string()) + } while (strzcmp16(attr.c_str(), attr.size(), targetType, targetTypeLen) == 0 + && (targetType = attrPrivate.c_str()) && (targetTypeLen = attrPrivate.size()) ); } @@ -5253,19 +5286,19 @@ bool ResTable::expandResourceRef(const char16_t* refStr, size_t refLen, *outType = *defType; } *outName = String16(p, end-p); - if(**outPackage == 0) { + if(outPackage->empty()) { if(outErrorMsg) { *outErrorMsg = "Resource package cannot be an empty string"; } return false; } - if(**outType == 0) { + if(outType->empty()) { if(outErrorMsg) { *outErrorMsg = "Resource type cannot be an empty string"; } return false; } - if(**outName == 0) { + if(outName->empty()) { if(outErrorMsg) { *outErrorMsg = "Resource id cannot be an empty string"; } @@ -5436,37 +5469,66 @@ bool ResTable::stringToInt(const char16_t* s, size_t len, Res_value* outValue) return U16StringToInt(s, len, outValue); } -bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue) -{ - while (len > 0 && isspace16(*s)) { - s++; - len--; +template <typename T> +bool parseFloatingPoint(const char16_t* inBuf, size_t inLen, char* tempBuf, + const char** outEnd, T& out){ + while (inLen > 0 && isspace16(*inBuf)) { + inBuf++; + inLen--; } - if (len <= 0) { + if (inLen <= 0) { return false; } - char buf[128]; int i=0; - while (len > 0 && *s != 0 && i < 126) { - if (*s > 255) { + while (inLen > 0 && *inBuf != 0 && i < 126) { + if (*inBuf > 255) { return false; } - buf[i++] = *s++; - len--; + tempBuf[i++] = *inBuf++; + inLen--; } - if (len > 0) { + if (inLen > 0) { + return false; + } + if ((tempBuf[0] < '0' || tempBuf[0] > '9') && tempBuf[0] != '.' && tempBuf[0] != '-' && tempBuf[0] != '+') { return false; } - if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') { + + tempBuf[i] = 0; + if constexpr(std::is_same_v<T, float>) { + out = strtof(tempBuf, (char**)outEnd); + } else { + out = strtod(tempBuf, (char**)outEnd); + } + return true; +} + +bool ResTable::stringToDouble(const char16_t* s, size_t len, double& d){ + char buf[128]; + const char* end = nullptr; + if (!parseFloatingPoint(s, len, buf, &end, d)) { return false; } - buf[i] = 0; - const char* end; - float f = strtof(buf, (char**)&end); + while (*end != 0 && isspace((unsigned char)*end)) { + end++; + } + + return *end == 0; +} + +bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue) +{ + char buf[128]; + const char* end = nullptr; + float f; + + if (!parseFloatingPoint(s, len, buf, &end, f)) { + return false; + } if (*end != 0 && !isspace((unsigned char)*end)) { // Might be a unit... @@ -5564,7 +5626,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, } } - //printf("Value for: %s\n", String8(s, len).string()); + //printf("Value for: %s\n", String8(s, len).c_str()); uint32_t l10nReq = ResTable_map::L10N_NOT_REQUIRED; uint32_t attrMin = 0x80000000, attrMax = 0x7fffffff; @@ -5619,7 +5681,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, // be to any other type; we just need to count on the client making // sure the referenced type is correct. - //printf("Looking up ref: %s\n", String8(s, len).string()); + //printf("Looking up ref: %s\n", String8(s, len).c_str()); // It's a reference! if (len == 5 && s[1]=='n' && s[2]=='u' && s[3]=='l' && s[4]=='l') { @@ -5659,8 +5721,8 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, } uint32_t specFlags = 0; - uint32_t rid = identifierForName(name.string(), name.size(), type.string(), - type.size(), package.string(), package.size(), &specFlags); + uint32_t rid = identifierForName(name.c_str(), name.size(), type.c_str(), + type.size(), package.c_str(), package.size(), &specFlags); if (rid != 0) { if (enforcePrivate) { if (accessor == NULL || accessor->getAssetsPackage() != package) { @@ -5679,8 +5741,8 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, Res_GETTYPE(rid), Res_GETENTRY(rid)); if (kDebugTableNoisy) { ALOGI("Incl %s:%s/%s: 0x%08x\n", - String8(package).string(), String8(type).string(), - String8(name).string(), rid); + String8(package).c_str(), String8(type).c_str(), + String8(name).c_str(), rid); } } @@ -5698,8 +5760,8 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, if (rid != 0) { if (kDebugTableNoisy) { ALOGI("Pckg %s:%s/%s: 0x%08x\n", - String8(package).string(), String8(type).string(), - String8(name).string(), rid); + String8(package).c_str(), String8(type).c_str(), + String8(name).c_str(), rid); } uint32_t packageId = Res_GETPACKAGE(rid) + 1; if (packageId == 0x00) { @@ -5788,7 +5850,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, } } else { outValue->data = color; - //printf("Color input=%s, output=0x%x\n", String8(s, len).string(), color); + //printf("Color input=%s, output=0x%x\n", String8(s, len).c_str(), color); return true; } } else { @@ -5800,8 +5862,8 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, #if 0 fprintf(stderr, "%s: Color ID %s value %s is not valid\n", "Resource File", //(const char*)in->getPrintableSource(), - String8(*curTag).string(), - String8(s, len).string()); + String8(*curTag).c_str(), + String8(s, len).c_str()); #endif return false; } @@ -5815,7 +5877,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, // be to any other type; we just need to count on the client making // sure the referenced type is correct. - //printf("Looking up attr: %s\n", String8(s, len).string()); + //printf("Looking up attr: %s\n", String8(s, len).c_str()); static const String16 attr16("attr"); String16 package, type, name; @@ -5828,13 +5890,13 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, } //printf("Pkg: %s, Type: %s, Name: %s\n", - // String8(package).string(), String8(type).string(), - // String8(name).string()); + // String8(package).c_str(), String8(type).c_str(), + // String8(name).c_str()); uint32_t specFlags = 0; uint32_t rid = - identifierForName(name.string(), name.size(), - type.string(), type.size(), - package.string(), package.size(), &specFlags); + identifierForName(name.c_str(), name.size(), + type.c_str(), type.size(), + package.c_str(), package.size(), &specFlags); if (rid != 0) { if (enforcePrivate) { if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) { @@ -5992,8 +6054,8 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, if (getResourceName(bag->map.name.ident, false, &rname)) { #if 0 printf("Matching %s against %s (0x%08x)\n", - String8(s, len).string(), - String8(rname.name, rname.nameLen).string(), + String8(s, len).c_str(), + String8(rname.name, rname.nameLen).c_str(), bag->map.name.ident); #endif if (strzcmp16(s, len, rname.name, rname.nameLen) == 0) { @@ -6036,7 +6098,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, while (pos < end && *pos != '|') { pos++; } - //printf("Looking for: %s\n", String8(start, pos-start).string()); + //printf("Looking for: %s\n", String8(start, pos-start).c_str()); const bag_entry* bagi = bag; ssize_t i; for (i=0; i<cnt; i++, bagi++) { @@ -6045,8 +6107,8 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, if (getResourceName(bagi->map.name.ident, false, &rname)) { #if 0 printf("Matching %s against %s (0x%08x)\n", - String8(start,pos-start).string(), - String8(rname.name, rname.nameLen).string(), + String8(start,pos-start).c_str(), + String8(rname.name, rname.nameLen).c_str(), bagi->map.name.ident); #endif if (strzcmp16(start, pos-start, rname.name, rname.nameLen) == 0) { @@ -6229,13 +6291,13 @@ bool ResTable::collectString(String16* outString, if (append) { outString->append(tmp); } else { - outString->setTo(tmp); + *outString = tmp; } } else { if (append) { outString->append(String16(s, len)); } else { - outString->setTo(s, len); + *outString = String16(s, len); } } @@ -6373,7 +6435,7 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMi } static bool compareString8AndCString(const String8& str, const char* cStr) { - return strcmp(str.string(), cStr) < 0; + return strcmp(str.c_str(), cStr) < 0; } void ResTable::getLocales(Vector<String8>* locales, bool includeSystemLocales, @@ -6387,7 +6449,7 @@ void ResTable::getLocales(Vector<String8>* locales, bool includeSystemLocales, const auto endIter = locales->end(); auto iter = std::lower_bound(beginIter, endIter, locale, compareString8AndCString); - if (iter == endIter || strcmp(iter->string(), locale) != 0) { + if (iter == endIter || strcmp(iter->c_str(), locale) != 0) { locales->insertAt(String8(locale), std::distance(beginIter, iter)); } }); @@ -6984,7 +7046,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, ResTable_config thisConfig; thisConfig.copyFromDtoH(type->config); ALOGI("Adding config to type %d: %s\n", type->id, - thisConfig.toString().string()); + thisConfig.toString().c_str()); } } } else { @@ -7061,7 +7123,7 @@ status_t DynamicRefTable::load(const ResTable_lib_header* const header) char16_t tmpName[sizeof(entry->packageName) / sizeof(char16_t)]; strcpy16_dtoh(tmpName, entry->packageName, sizeof(entry->packageName) / sizeof(char16_t)); if (kDebugLibNoisy) { - ALOGV("Found lib entry %s with id %d\n", String8(tmpName).string(), + ALOGV("Found lib entry %s with id %d\n", String8(tmpName).c_str(), dtohl(entry->packageId)); } if (packageId >= 256) { @@ -7340,7 +7402,7 @@ status_t ResTable::createIdmap(const ResTable& targetResTable, current_res.nameLen, current_res.type, current_res.typeLen, - targetPackageName.string(), + targetPackageName.c_str(), targetPackageName.size(), &typeSpecFlags); @@ -7438,16 +7500,16 @@ bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes, *pOverlayCrc = dtohl(map[3]); } if (pTargetPath) { - pTargetPath->setTo(reinterpret_cast<const char*>(map + 4)); + *pTargetPath = reinterpret_cast<const char*>(map + 4); } if (pOverlayPath) { - pOverlayPath->setTo(reinterpret_cast<const char*>(map + 4 + 256 / sizeof(uint32_t))); + *pOverlayPath = reinterpret_cast<const char*>(map + 4 + 256 / sizeof(uint32_t)); } return true; } -#define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string()) +#define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).c_str()) #define CHAR16_ARRAY_EQ(constant, var, len) \ (((len) == (sizeof(constant)/sizeof((constant)[0]))) && (0 == memcmp((var), (constant), (len)))) @@ -7542,13 +7604,13 @@ void ResTable::print_value(const Package* pkg, const Res_value& value) const const char* str8 = UnpackOptionalString(pkg->header->values.string8At( value.data), &len); if (str8 != NULL) { - printf("(string8) \"%s\"\n", normalizeForOutput(str8).string()); + printf("(string8) \"%s\"\n", normalizeForOutput(str8).c_str()); } else { const char16_t* str16 = UnpackOptionalString(pkg->header->values.stringAt( value.data), &len); if (str16 != NULL) { printf("(string16) \"%s\"\n", - normalizeForOutput(String8(str16, len).string()).string()); + normalizeForOutput(String8(str16, len).c_str()).c_str()); } else { printf("(string) null\n"); } @@ -7589,7 +7651,7 @@ void ResTable::print(bool inclValues) const const PackageGroup* pg = mPackageGroups[pgIndex]; printf("Package Group %d id=0x%02x packageCount=%d name=%s\n", (int)pgIndex, pg->id, (int)pg->packages.size(), - String8(pg->name).string()); + String8(pg->name).c_str()); const KeyedVector<String16, uint8_t>& refEntries = pg->dynamicRefTable.entries(); const size_t refEntryCount = refEntries.size(); @@ -7598,7 +7660,7 @@ void ResTable::print(bool inclValues) const for (size_t refIndex = 0; refIndex < refEntryCount; refIndex++) { printf(" 0x%02x -> %s\n", refEntries.valueAt(refIndex), - String8(refEntries.keyAt(refIndex)).string()); + String8(refEntries.keyAt(refIndex)).c_str()); } printf("\n"); } @@ -7624,7 +7686,7 @@ void ResTable::print(bool inclValues) const strcpy16_dtoh(tmpName, pkg->package->name, sizeof(pkg->package->name)/sizeof(pkg->package->name[0])); printf(" Package %d id=0x%02x name=%s\n", (int)pkgIndex, - pkg->package->id, String8(tmpName).string()); + pkg->package->id, String8(tmpName).c_str()); } for (size_t typeIndex = 0; typeIndex < pg->types.size(); typeIndex++) { @@ -7666,7 +7728,7 @@ void ResTable::print(bool inclValues) const printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n", resID, CHAR16_TO_CSTR(resName.package, resName.packageLen), - type8.string(), name8.string(), + type8.c_str(), name8.c_str(), dtohl(typeConfigs->typeSpecFlags[entryIndex])); } else { printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID); @@ -7687,7 +7749,7 @@ void ResTable::print(bool inclValues) const String8 configStr = thisConfig.toString(); printf(" config %s", configStr.size() > 0 - ? configStr.string() : "(default)"); + ? configStr.c_str() : "(default)"); if (type->flags != 0u) { printf(" flags=0x%02x", type->flags); if (type->flags & ResTable_type::FLAG_SPARSE) { @@ -7761,7 +7823,7 @@ void ResTable::print(bool inclValues) const } printf(" resource 0x%08x %s:%s/%s: ", resID, CHAR16_TO_CSTR(resName.package, resName.packageLen), - type8.string(), name8.string()); + type8.c_str(), name8.c_str()); } else { printf(" INVALID RESOURCE 0x%08x: ", resID); } diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp index 52e7a70521a1..d7b5914130ee 100644 --- a/libs/androidfw/ZipFileRO.cpp +++ b/libs/androidfw/ZipFileRO.cpp @@ -40,17 +40,24 @@ class _ZipEntryRO { public: ZipEntry entry; std::string_view name; - void *cookie; + void *cookie = nullptr; - _ZipEntryRO() : cookie(NULL) {} + _ZipEntryRO() = default; ~_ZipEntryRO() { - EndIteration(cookie); + EndIteration(cookie); + } + + android::ZipEntryRO convertToPtr() { + _ZipEntryRO* result = new _ZipEntryRO; + result->entry = std::move(this->entry); + result->name = std::move(this->name); + result->cookie = std::exchange(this->cookie, nullptr); + return result; } private: - _ZipEntryRO(const _ZipEntryRO& other); - _ZipEntryRO& operator=(const _ZipEntryRO& other); + DISALLOW_COPY_AND_ASSIGN(_ZipEntryRO); }; ZipFileRO::~ZipFileRO() { @@ -94,17 +101,15 @@ ZipFileRO::~ZipFileRO() { ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const { - _ZipEntryRO* data = new _ZipEntryRO; - - data->name = entryName; + _ZipEntryRO data; + data.name = entryName; - const int32_t error = FindEntry(mHandle, entryName, &(data->entry)); + const int32_t error = FindEntry(mHandle, entryName, &(data.entry)); if (error) { - delete data; - return NULL; + return nullptr; } - return (ZipEntryRO) data; + return data.convertToPtr(); } /* @@ -143,35 +148,50 @@ bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod, } bool ZipFileRO::startIteration(void** cookie) { - return startIteration(cookie, NULL, NULL); + return startIteration(cookie, nullptr, nullptr); } -bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix) -{ - _ZipEntryRO* ze = new _ZipEntryRO; - int32_t error = StartIteration(mHandle, &(ze->cookie), +bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix) { + auto result = startIterationOrError(prefix, suffix); + if (!result.ok()) { + return false; + } + *cookie = result.value(); + return true; +} + +base::expected<void*, int32_t> +ZipFileRO::startIterationOrError(const char* prefix, const char* suffix) { + _ZipEntryRO ze; + int32_t error = StartIteration(mHandle, &(ze.cookie), prefix ? prefix : "", suffix ? suffix : ""); if (error) { ALOGW("Could not start iteration over %s: %s", mFileName != NULL ? mFileName : "<null>", ErrorCodeString(error)); - delete ze; - return false; + return base::unexpected(error); } - *cookie = ze; - return true; + return ze.convertToPtr(); } -ZipEntryRO ZipFileRO::nextEntry(void* cookie) -{ +ZipEntryRO ZipFileRO::nextEntry(void* cookie) { + auto result = nextEntryOrError(cookie); + if (!result.ok()) { + return nullptr; + } + return result.value(); +} + +base::expected<ZipEntryRO, int32_t> ZipFileRO::nextEntryOrError(void* cookie) { _ZipEntryRO* ze = reinterpret_cast<_ZipEntryRO*>(cookie); int32_t error = Next(ze->cookie, &(ze->entry), &(ze->name)); if (error) { if (error != -1) { ALOGW("Error iteration over %s: %s", mFileName != NULL ? mFileName : "<null>", ErrorCodeString(error)); + return base::unexpected(error); } - return NULL; + return nullptr; } return &(ze->entry); diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h index 19febcdee77e..f3776b5401f3 100644 --- a/libs/androidfw/include/androidfw/Asset.h +++ b/libs/androidfw/include/androidfw/Asset.h @@ -135,7 +135,7 @@ public: * This is NOT intended to be used for anything except debug output. * DO NOT try to parse this or use it to open a file. */ - const char* getAssetSource(void) const { return mAssetSource.string(); } + const char* getAssetSource(void) const { return mAssetSource.c_str(); } /* * Create the asset from a file descriptor. diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index fc391bc2ce67..d9ff35b49e0a 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -100,7 +100,7 @@ class AssetManager2 { using ApkAssetsWPtr = wp<const ApkAssets>; using ApkAssetsList = std::span<const ApkAssetsPtr>; - AssetManager2() = default; + AssetManager2(); explicit AssetManager2(AssetManager2&& other) = default; AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration); @@ -156,10 +156,14 @@ class AssetManager2 { // Sets/resets the configuration for this AssetManager. This will cause all // caches that are related to the configuration change to be invalidated. - void SetConfiguration(const ResTable_config& configuration); + void SetConfigurations(std::vector<ResTable_config> configurations); - inline const ResTable_config& GetConfiguration() const { - return configuration_; + inline const std::vector<ResTable_config>& GetConfigurations() const { + return configurations_; + } + + inline void SetDefaultLocale(uint32_t default_locale) { + default_locale_ = default_locale; } // Returns all configurations for which there are resources defined, or an I/O error if reading @@ -243,9 +247,14 @@ class AssetManager2 { friend AssetManager2; friend Theme; SelectedValue() = default; - SelectedValue(const ResolvedBag* bag, const ResolvedBag::Entry& entry) : - cookie(entry.cookie), data(entry.value.data), type(entry.value.dataType), - flags(bag->type_spec_flags), resid(0U), config({}) {}; + SelectedValue(const ResolvedBag* bag, const ResolvedBag::Entry& entry) + : cookie(entry.cookie), + data(entry.value.data), + type(entry.value.dataType), + flags(bag->type_spec_flags), + resid(0U), + config() { + } // The cookie representing the ApkAssets in which the value resides. ApkAssetsCookie cookie = kInvalidCookie; @@ -327,7 +336,8 @@ class AssetManager2 { // resource data failed. base::expected<uint32_t, NullOrIOError> GetResourceTypeSpecFlags(uint32_t resid) const; - const std::vector<uint32_t> GetBagResIdStack(uint32_t resid) const; + base::expected<const std::vector<uint32_t>*, NullOrIOError> GetBagResIdStack( + uint32_t resid) const; // Resets the resource resolution structures in preparation for the next resource retrieval. void ResetResourceResolution() const; @@ -459,9 +469,11 @@ class AssetManager2 { // without taking too much memory. std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_; - // The current configuration set for this AssetManager. When this changes, cached resources + uint32_t default_locale_; + + // The current configurations set for this AssetManager. When this changes, cached resources // may need to be purged. - ResTable_config configuration_ = {}; + std::vector<ResTable_config> configurations_; // Cached set of bags. These are cached because they can inherit keys from parent bags, // which involves some calculation. @@ -495,10 +507,10 @@ class AssetManager2 { // Marks what kind of override this step was. Type type; + ApkAssetsCookie cookie = kInvalidCookie; + // Built name of configuration for this step. String8 config_name; - - ApkAssetsCookie cookie = kInvalidCookie; }; // Last resolved resource ID. diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h index 7fbd7c08ea69..83a80ced855b 100644 --- a/libs/androidfw/include/androidfw/ConfigDescription.h +++ b/libs/androidfw/include/androidfw/ConfigDescription.h @@ -213,7 +213,7 @@ inline bool ConfigDescription::operator>(const ConfigDescription& o) const { inline ::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) { - return out << o.toString().string(); + return out << o.toString().c_str(); } } // namespace android diff --git a/libs/androidfw/include/androidfw/Errors.h b/libs/androidfw/include/androidfw/Errors.h index 948162d10480..6667747fc581 100644 --- a/libs/androidfw/include/androidfw/Errors.h +++ b/libs/androidfw/include/androidfw/Errors.h @@ -34,7 +34,7 @@ using NullOrIOError = std::variant<std::nullopt_t, IOError>; // Checks whether the result holds an unexpected I/O error. template <typename T> -static inline bool IsIOError(const base::expected<T, NullOrIOError> result) { +static inline bool IsIOError(const base::expected<T, NullOrIOError>& result) { return !result.has_value() && std::holds_alternative<IOError>(result.error()); } diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h index 4d5844eaa069..865a298f8389 100644 --- a/libs/androidfw/include/androidfw/IDiagnostics.h +++ b/libs/androidfw/include/androidfw/IDiagnostics.h @@ -86,6 +86,17 @@ struct IDiagnostics { DiagMessageActual actual = message.Build(); Log(Level::Note, actual); } + + virtual void SetVerbose(bool val) { + verbose_ = val; + } + + virtual bool IsVerbose() { + return verbose_; + } + + private: + bool verbose_ = false; }; class SourcePathDiagnostics : public IDiagnostics { @@ -105,6 +116,14 @@ class SourcePathDiagnostics : public IDiagnostics { return error; } + void SetVerbose(bool val) override { + diag_->SetVerbose(val); + } + + bool IsVerbose() override { + return diag_->IsVerbose(); + } + private: Source source_; IDiagnostics* diag_; diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 4d12885ad291..3a7287187781 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -96,6 +96,9 @@ enum : package_property_t { // The apk assets is owned by the application running in this process and incremental crash // protections for this APK must be disabled. PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1U << 4U, + + // The apk assets only contain the overlayable declarations information. + PROPERTY_ONLY_OVERLAYABLES = 1U << 5U, }; struct OverlayableInfo { diff --git a/libs/androidfw/include/androidfw/ObbFile.h b/libs/androidfw/include/androidfw/ObbFile.h index 3dbf997dc367..38ece5c1546f 100644 --- a/libs/androidfw/include/androidfw/ObbFile.h +++ b/libs/androidfw/include/androidfw/ObbFile.h @@ -43,10 +43,6 @@ public: bool removeFrom(const char* filename); bool removeFrom(int fd); - const char* getFileName() const { - return mFileName; - } - const String8 getPackageName() const { return mPackageName; } @@ -127,8 +123,6 @@ private: /* The encryption salt. */ unsigned char mSalt[8]; - const char* mFileName; - size_t mFooterStart; bool parseObbFile(int fd); diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 631bda4f886c..fdb355192676 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1375,6 +1375,8 @@ struct ResTable_config // match the requested configuration at all. bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const; + bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const; + String8 toString() const; }; @@ -1870,6 +1872,8 @@ struct FabricatedOverlayEntryParameters { DataValue data_value; std::string data_string_value; std::optional<android::base::borrowed_fd> data_binary_value; + off64_t binary_data_offset; + size_t binary_data_size; std::string configuration; }; @@ -2162,6 +2166,7 @@ public: static bool stringToInt(const char16_t* s, size_t len, Res_value* outValue); static bool stringToFloat(const char16_t* s, size_t len, Res_value* outValue); + static bool stringToDouble(const char16_t* s, size_t len, double& outValue); // Used with stringToValue. class Accessor diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h index 10f6d0655bf4..be1f98f4843d 100644 --- a/libs/androidfw/include/androidfw/ZipFileRO.h +++ b/libs/androidfw/include/androidfw/ZipFileRO.h @@ -37,6 +37,8 @@ #include <unistd.h> #include <time.h> +#include <android-base/expected.h> + #include <util/map_ptr.h> #include <utils/Compat.h> @@ -102,6 +104,11 @@ public: */ bool startIteration(void** cookie); bool startIteration(void** cookie, const char* prefix, const char* suffix); + /* + * Same as above, but returns the error code in case of failure. + * #see libziparchive/zip_error.h. + */ + base::expected<void*, int32_t> startIterationOrError(const char* prefix, const char* suffix); /** * Return the next entry in iteration order, or NULL if there are no more @@ -109,6 +116,12 @@ public: */ ZipEntryRO nextEntry(void* cookie); + /** + * Same as above, but returns the error code in case of failure. + * #see libziparchive/zip_error.h. + */ + base::expected<ZipEntryRO, int32_t> nextEntryOrError(void* cookie); + void endIteration(void* cookie); void releaseEntry(ZipEntryRO entry) const; diff --git a/libs/androidfw/include_pathutils/androidfw/PathUtils.h b/libs/androidfw/include_pathutils/androidfw/PathUtils.h new file mode 100644 index 000000000000..4debe8d8da6d --- /dev/null +++ b/libs/androidfw/include_pathutils/androidfw/PathUtils.h @@ -0,0 +1,98 @@ +/* + * 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 <utils/String8.h> + +/* This library contains path manipulation functions that are used only by androidfw and aapt. + * When it's possible, migrate all uses to std::filesystem::path. + */ + +namespace android { + +/** + * Get just the filename component. + * + * DEPRECATED: use std::filesystem::path::filename + * + * "/tmp/foo/bar.c" --> "bar.c" + */ +String8 getPathLeaf(const String8& str); + +/** + * Remove the last (file name) component, leaving just the directory + * name. + * + * DEPRECATED: use std::filesystem::path::parent_path + * + * "/tmp/foo/bar.c" --> "/tmp/foo" + * "/tmp" --> "" // ????? shouldn't this be "/" ???? XXX + * "bar.c" --> "" + */ +String8 getPathDir(const String8& str); + +/** + * Return the filename extension. This is the last '.' and any number + * of characters that follow it. The '.' is included in case we + * decide to expand our definition of what constitutes an extension. + * + * DEPRECATED: use std::filesystem::path::extension + * + * "/tmp/foo/bar.c" --> ".c" + * "/tmp" --> "" + * "/tmp/foo.bar/baz" --> "" + * "foo.jpeg" --> ".jpeg" + * "foo." --> "" + */ +String8 getPathExtension(const String8& str); + +/** + * Return the path without the extension. Rules for what constitutes + * an extension are described in the comment for getPathExtension(). + * + * DEPRECATED: use std::filesystem::path::stem and std::filesystem::path::parent_path + * + * "/tmp/foo/bar.c" --> "/tmp/foo/bar" + */ +String8 getBasePath(const String8& str); + +/** + * Add a component to the pathname. We guarantee that there is + * exactly one path separator between the old path and the new. + * If there is no existing name, we just copy the new name in. + * + * DEPRECATED: use std::filesystem::path::operator/= + * + * If leaf is a fully qualified path (i.e. starts with '/', it + * replaces whatever was there before. + */ +String8& appendPath(String8& str, const char* leaf); +inline String8& appendPath(String8& str, const String8& leaf) { + return appendPath(str, leaf.c_str()); +} + +/** + * Like appendPath(), but does not affect this string. Returns a new one instead. + * + * DEPRECATED: use std::filesystem::operator/ + */ +inline String8 appendPathCopy(String8 str, const char* leaf) { return appendPath(str, leaf); } +inline String8 appendPathCopy(String8 str, const String8& leaf) { + return appendPath(str, leaf.c_str()); +} + +} // namespace android diff --git a/libs/androidfw/tests/ApkParsing_test.cpp b/libs/androidfw/tests/ApkParsing_test.cpp index 62e88c619e5c..ac1dc9b88463 100644 --- a/libs/androidfw/tests/ApkParsing_test.cpp +++ b/libs/androidfw/tests/ApkParsing_test.cpp @@ -74,4 +74,10 @@ TEST(ApkParsingTest, InvalidFileAtRoot) { auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); ASSERT_THAT(lastSlash, IsNull()); } + +TEST(ApkParsingTest, InvalidPrefix) { + const char* path = "assets/libhello.so"; + auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + ASSERT_THAT(lastSlash, IsNull()); +} }
\ No newline at end of file diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp index 6fae72a6d10e..2caa98c35971 100644 --- a/libs/androidfw/tests/AssetManager2_bench.cpp +++ b/libs/androidfw/tests/AssetManager2_bench.cpp @@ -228,10 +228,12 @@ static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) { ResTable_config config; memset(&config, 0, sizeof(config)); + std::vector<ResTable_config> configs; + configs.push_back(config); while (state.KeepRunning()) { - config.sdkVersion = ~config.sdkVersion; - assets.SetConfiguration(config); + configs[0].sdkVersion = ~configs[0].sdkVersion; + assets.SetConfigurations(configs); } } BENCHMARK(BM_AssetManagerSetConfigurationFramework); diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index df3fa02ce44c..c62f095e9dac 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -113,7 +113,7 @@ TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) { desired_config.language[1] = 'e'; AssetManager2 assetmanager; - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_}); auto value = assetmanager.GetResource(basic::R::string::test1); @@ -137,7 +137,7 @@ TEST_F(AssetManager2Test, FindsResourceFromMultipleApkAssets) { desired_config.language[1] = 'e'; AssetManager2 assetmanager; - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_}); auto value = assetmanager.GetResource(basic::R::string::test1); @@ -466,10 +466,10 @@ TEST_F(AssetManager2Test, ResolveDeepIdReference) { TEST_F(AssetManager2Test, DensityOverride) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_}); - assetmanager.SetConfiguration({ + assetmanager.SetConfigurations({{ .density = ResTable_config::DENSITY_XHIGH, .sdkVersion = 21, - }); + }}); auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/); ASSERT_TRUE(value.has_value()); @@ -721,7 +721,7 @@ TEST_F(AssetManager2Test, GetLastPathWithoutEnablingReturnsEmpty) { ResTable_config desired_config; AssetManager2 assetmanager; - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_}); assetmanager.SetResourceResolutionLoggingEnabled(false); @@ -736,7 +736,7 @@ TEST_F(AssetManager2Test, GetLastPathWithoutResolutionReturnsEmpty) { ResTable_config desired_config; AssetManager2 assetmanager; - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_}); auto result = assetmanager.GetLastResourceResolution(); @@ -751,7 +751,7 @@ TEST_F(AssetManager2Test, GetLastPathWithSingleApkAssets) { AssetManager2 assetmanager; assetmanager.SetResourceResolutionLoggingEnabled(true); - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_}); auto value = assetmanager.GetResource(basic::R::string::test1); @@ -774,7 +774,7 @@ TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) { AssetManager2 assetmanager; assetmanager.SetResourceResolutionLoggingEnabled(true); - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_}); auto value = assetmanager.GetResource(basic::R::string::test1); @@ -796,7 +796,7 @@ TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) { AssetManager2 assetmanager; assetmanager.SetResourceResolutionLoggingEnabled(true); - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({basic_assets_}); auto value = assetmanager.GetResource(basic::R::string::test1); @@ -817,7 +817,7 @@ TEST_F(AssetManager2Test, GetOverlayablesToString) { AssetManager2 assetmanager; assetmanager.SetResourceResolutionLoggingEnabled(true); - assetmanager.SetConfiguration(desired_config); + assetmanager.SetConfigurations({desired_config}); assetmanager.SetApkAssets({overlayable_assets_}); const auto map = assetmanager.GetOverlayableMapForPackage(0x7f); diff --git a/libs/androidfw/tests/BackupData_test.cpp b/libs/androidfw/tests/BackupData_test.cpp index e25b616dcbd9..7d3a3411d81d 100644 --- a/libs/androidfw/tests/BackupData_test.cpp +++ b/libs/androidfw/tests/BackupData_test.cpp @@ -56,10 +56,10 @@ protected: mFilename.append(m_external_storage); mFilename.append(TEST_FILENAME); - ::unlink(mFilename.string()); - int fd = ::open(mFilename.string(), O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + ::unlink(mFilename.c_str()); + int fd = ::open(mFilename.c_str(), O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd < 0) { - FAIL() << "Couldn't create " << mFilename.string() << " for writing"; + FAIL() << "Couldn't create " << mFilename.c_str() << " for writing"; } mKey1 = String8(KEY1); mKey2 = String8(KEY2); @@ -72,7 +72,7 @@ protected: }; TEST_F(BackupDataTest, WriteAndReadSingle) { - int fd = ::open(mFilename.string(), O_WRONLY); + int fd = ::open(mFilename.c_str(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); EXPECT_EQ(NO_ERROR, writer->WriteEntityHeader(mKey1, sizeof(DATA1))) @@ -81,7 +81,7 @@ TEST_F(BackupDataTest, WriteAndReadSingle) { << "WriteEntityData returned an error"; ::close(fd); - fd = ::open(mFilename.string(), O_RDONLY); + fd = ::open(mFilename.c_str(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); EXPECT_EQ(NO_ERROR, reader->Status()) << "Reader ctor failed"; @@ -114,7 +114,7 @@ TEST_F(BackupDataTest, WriteAndReadSingle) { } TEST_F(BackupDataTest, WriteAndReadMultiple) { - int fd = ::open(mFilename.string(), O_WRONLY); + int fd = ::open(mFilename.c_str(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, sizeof(DATA1)); writer->WriteEntityData(DATA1, sizeof(DATA1)); @@ -122,7 +122,7 @@ TEST_F(BackupDataTest, WriteAndReadMultiple) { writer->WriteEntityData(DATA2, sizeof(DATA2)); ::close(fd); - fd = ::open(mFilename.string(), O_RDONLY); + fd = ::open(mFilename.c_str(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -162,7 +162,7 @@ TEST_F(BackupDataTest, WriteAndReadMultiple) { } TEST_F(BackupDataTest, SkipEntity) { - int fd = ::open(mFilename.string(), O_WRONLY); + int fd = ::open(mFilename.c_str(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, sizeof(DATA1)); writer->WriteEntityData(DATA1, sizeof(DATA1)); @@ -172,7 +172,7 @@ TEST_F(BackupDataTest, SkipEntity) { writer->WriteEntityData(DATA3, sizeof(DATA3)); ::close(fd); - fd = ::open(mFilename.string(), O_RDONLY); + fd = ::open(mFilename.c_str(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -217,14 +217,14 @@ TEST_F(BackupDataTest, SkipEntity) { } TEST_F(BackupDataTest, DeleteEntity) { - int fd = ::open(mFilename.string(), O_WRONLY); + int fd = ::open(mFilename.c_str(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, sizeof(DATA1)); writer->WriteEntityData(DATA1, sizeof(DATA1)); writer->WriteEntityHeader(mKey2, -1); ::close(fd); - fd = ::open(mFilename.string(), O_RDONLY); + fd = ::open(mFilename.c_str(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -256,7 +256,7 @@ TEST_F(BackupDataTest, DeleteEntity) { } TEST_F(BackupDataTest, EneityAfterDelete) { - int fd = ::open(mFilename.string(), O_WRONLY); + int fd = ::open(mFilename.c_str(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, sizeof(DATA1)); writer->WriteEntityData(DATA1, sizeof(DATA1)); @@ -265,7 +265,7 @@ TEST_F(BackupDataTest, EneityAfterDelete) { writer->WriteEntityData(DATA3, sizeof(DATA3)); ::close(fd); - fd = ::open(mFilename.string(), O_RDONLY); + fd = ::open(mFilename.c_str(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -317,7 +317,7 @@ TEST_F(BackupDataTest, EneityAfterDelete) { } TEST_F(BackupDataTest, OnlyDeleteEntities) { - int fd = ::open(mFilename.string(), O_WRONLY); + int fd = ::open(mFilename.c_str(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, -1); writer->WriteEntityHeader(mKey2, -1); @@ -325,7 +325,7 @@ TEST_F(BackupDataTest, OnlyDeleteEntities) { writer->WriteEntityHeader(mKey4, -1); ::close(fd); - fd = ::open(mFilename.string(), O_RDONLY); + fd = ::open(mFilename.c_str(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -385,13 +385,13 @@ TEST_F(BackupDataTest, OnlyDeleteEntities) { } TEST_F(BackupDataTest, ReadDeletedEntityData) { - int fd = ::open(mFilename.string(), O_WRONLY); + int fd = ::open(mFilename.c_str(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, -1); writer->WriteEntityHeader(mKey2, -1); ::close(fd); - fd = ::open(mFilename.string(), O_RDONLY); + fd = ::open(mFilename.c_str(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp index b97dd96f8934..8b883f4ed1df 100644 --- a/libs/androidfw/tests/BenchmarkHelpers.cpp +++ b/libs/androidfw/tests/BenchmarkHelpers.cpp @@ -66,7 +66,7 @@ void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_ AssetManager2 assetmanager; assetmanager.SetApkAssets(apk_assets); if (config != nullptr) { - assetmanager.SetConfiguration(*config); + assetmanager.SetConfigurations({*config}); } while (state.KeepRunning()) { diff --git a/libs/androidfw/tests/CommonHelpers.cpp b/libs/androidfw/tests/CommonHelpers.cpp index 3396729536a4..10138de0843c 100644 --- a/libs/androidfw/tests/CommonHelpers.cpp +++ b/libs/androidfw/tests/CommonHelpers.cpp @@ -60,7 +60,7 @@ const std::string& GetTestDataPath() { std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx) { auto str = pool->string8ObjectAt(idx); CHECK(str.has_value()) << "failed to find string entry"; - return std::string(str->string(), str->length()); + return std::string(str->c_str(), str->length()); } } // namespace android diff --git a/libs/androidfw/tests/ConfigDescription_test.cpp b/libs/androidfw/tests/ConfigDescription_test.cpp index f5c01e5d9b68..07bd17568993 100644 --- a/libs/androidfw/tests/ConfigDescription_test.cpp +++ b/libs/androidfw/tests/ConfigDescription_test.cpp @@ -50,10 +50,10 @@ TEST(ConfigDescriptionTest, ParseFailWhenQualifiersHaveTrailingDash) { TEST(ConfigDescriptionTest, ParseBasicQualifiers) { ConfigDescription config; EXPECT_TRUE(TestParse("", &config)); - EXPECT_EQ(std::string(""), config.toString().string()); + EXPECT_EQ(std::string(""), config.toString().c_str()); EXPECT_TRUE(TestParse("fr-land", &config)); - EXPECT_EQ(std::string("fr-land"), config.toString().string()); + EXPECT_EQ(std::string("fr-land"), config.toString().c_str()); EXPECT_TRUE( TestParse("mcc310-pl-sw720dp-normal-long-port-night-" @@ -61,22 +61,22 @@ TEST(ConfigDescriptionTest, ParseBasicQualifiers) { &config)); EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-" "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), - config.toString().string()); + config.toString().c_str()); } TEST(ConfigDescriptionTest, ParseLocales) { ConfigDescription config; EXPECT_TRUE(TestParse("en-rUS", &config)); - EXPECT_EQ(std::string("en-rUS"), config.toString().string()); + EXPECT_EQ(std::string("en-rUS"), config.toString().c_str()); } TEST(ConfigDescriptionTest, ParseQualifierAddedInApi13) { ConfigDescription config; EXPECT_TRUE(TestParse("sw600dp", &config)); - EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string()); + EXPECT_EQ(std::string("sw600dp-v13"), config.toString().c_str()); EXPECT_TRUE(TestParse("sw600dp-v8", &config)); - EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string()); + EXPECT_EQ(std::string("sw600dp-v13"), config.toString().c_str()); } TEST(ConfigDescriptionTest, ParseCarAttribute) { @@ -91,13 +91,13 @@ TEST(ConfigDescriptionTest, TestParsingRoundQualifier) { EXPECT_EQ(android::ResTable_config::SCREENROUND_YES, config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND); EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion); - EXPECT_EQ(std::string("round-v23"), config.toString().string()); + EXPECT_EQ(std::string("round-v23"), config.toString().c_str()); EXPECT_TRUE(TestParse("notround", &config)); EXPECT_EQ(android::ResTable_config::SCREENROUND_NO, config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND); EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion); - EXPECT_EQ(std::string("notround-v23"), config.toString().string()); + EXPECT_EQ(std::string("notround-v23"), config.toString().c_str()); } TEST(ConfigDescriptionTest, TestWideColorGamutQualifier) { @@ -106,13 +106,13 @@ TEST(ConfigDescriptionTest, TestWideColorGamutQualifier) { EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_YES, config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT); EXPECT_EQ(SDK_O, config.sdkVersion); - EXPECT_EQ(std::string("widecg-v26"), config.toString().string()); + EXPECT_EQ(std::string("widecg-v26"), config.toString().c_str()); EXPECT_TRUE(TestParse("nowidecg", &config)); EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_NO, config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT); EXPECT_EQ(SDK_O, config.sdkVersion); - EXPECT_EQ(std::string("nowidecg-v26"), config.toString().string()); + EXPECT_EQ(std::string("nowidecg-v26"), config.toString().c_str()); } TEST(ConfigDescriptionTest, TestHdrQualifier) { @@ -121,13 +121,13 @@ TEST(ConfigDescriptionTest, TestHdrQualifier) { EXPECT_EQ(android::ResTable_config::HDR_YES, config.colorMode & android::ResTable_config::MASK_HDR); EXPECT_EQ(SDK_O, config.sdkVersion); - EXPECT_EQ(std::string("highdr-v26"), config.toString().string()); + EXPECT_EQ(std::string("highdr-v26"), config.toString().c_str()); EXPECT_TRUE(TestParse("lowdr", &config)); EXPECT_EQ(android::ResTable_config::HDR_NO, config.colorMode & android::ResTable_config::MASK_HDR); EXPECT_EQ(SDK_O, config.sdkVersion); - EXPECT_EQ(std::string("lowdr-v26"), config.toString().string()); + EXPECT_EQ(std::string("lowdr-v26"), config.toString().c_str()); } TEST(ConfigDescriptionTest, ParseVrAttribute) { @@ -135,7 +135,7 @@ TEST(ConfigDescriptionTest, ParseVrAttribute) { EXPECT_TRUE(TestParse("vrheadset", &config)); EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_VR_HEADSET, config.uiMode); EXPECT_EQ(SDK_O, config.sdkVersion); - EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string()); + EXPECT_EQ(std::string("vrheadset-v26"), config.toString().c_str()); } static inline ConfigDescription ParseConfigOrDie(android::StringPiece str) { @@ -159,17 +159,17 @@ TEST(ConfigDescriptionTest, TestGrammaticalGenderQualifier) { EXPECT_TRUE(TestParse("feminine", &config)); EXPECT_EQ(android::ResTable_config::GRAMMATICAL_GENDER_FEMININE, config.grammaticalInflection); EXPECT_EQ(SDK_U, config.sdkVersion); - EXPECT_EQ(std::string("feminine-v34"), config.toString().string()); + EXPECT_EQ(std::string("feminine-v34"), config.toString().c_str()); EXPECT_TRUE(TestParse("masculine", &config)); EXPECT_EQ(android::ResTable_config::GRAMMATICAL_GENDER_MASCULINE, config.grammaticalInflection); EXPECT_EQ(SDK_U, config.sdkVersion); - EXPECT_EQ(std::string("masculine-v34"), config.toString().string()); + EXPECT_EQ(std::string("masculine-v34"), config.toString().c_str()); EXPECT_TRUE(TestParse("neuter", &config)); EXPECT_EQ(android::ResTable_config::GRAMMATICAL_GENDER_NEUTER, config.grammaticalInflection); EXPECT_EQ(SDK_U, config.sdkVersion); - EXPECT_EQ(std::string("neuter-v34"), config.toString().string()); + EXPECT_EQ(std::string("neuter-v34"), config.toString().c_str()); } } // namespace android diff --git a/libs/androidfw/tests/Generic_bench.cpp b/libs/androidfw/tests/Generic_bench.cpp new file mode 100644 index 000000000000..4c978e889f83 --- /dev/null +++ b/libs/androidfw/tests/Generic_bench.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2016 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 <stdint.h> + +#include <map> +#include <unordered_map> + +#include "benchmark/benchmark.h" + +namespace android { + +template <class Map = std::unordered_map<uint32_t, std::vector<uint32_t>>> +static Map prepare_map() { + Map map; + std::vector<uint32_t> vec; + for (int i = 0; i < 1000; ++i) { + map.emplace(i, vec); + } + return map; +} + +static void BM_hashmap_emplace_same(benchmark::State& state) { + auto map = prepare_map<>(); + auto val = map.size() - 1; + std::vector<uint32_t> vec; + for (auto&& _ : state) { + benchmark::DoNotOptimize(map.emplace(val, vec)); + } +} +BENCHMARK(BM_hashmap_emplace_same); +static void BM_hashmap_try_emplace_same(benchmark::State& state) { + auto map = prepare_map(); + auto val = map.size() - 1; + for (auto&& _ : state) { + benchmark::DoNotOptimize(map.try_emplace(val)); + } +} +BENCHMARK(BM_hashmap_try_emplace_same); +static void BM_hashmap_find(benchmark::State& state) { + auto map = prepare_map<>(); + auto val = map.size() - 1; + for (auto&& _ : state) { + benchmark::DoNotOptimize(map.find(val)); + } +} +BENCHMARK(BM_hashmap_find); + +static void BM_hashmap_emplace_diff(benchmark::State& state) { + auto map = prepare_map<>(); + std::vector<uint32_t> vec; + auto i = map.size(); + for (auto&& _ : state) { + map.emplace(i++, vec); + } +} +BENCHMARK(BM_hashmap_emplace_diff); +static void BM_hashmap_try_emplace_diff(benchmark::State& state) { + auto map = prepare_map(); + auto i = map.size(); + for (auto&& _ : state) { + map.try_emplace(i++); + } +} +BENCHMARK(BM_hashmap_try_emplace_diff); +static void BM_hashmap_find_emplace_diff(benchmark::State& state) { + auto map = prepare_map<>(); + std::vector<uint32_t> vec; + auto i = map.size(); + for (auto&& _ : state) { + if (map.find(i++) == map.end()) { + map.emplace(i - 1, vec); + } + } +} +BENCHMARK(BM_hashmap_find_emplace_diff); + +static void BM_treemap_emplace_same(benchmark::State& state) { + auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>(); + auto val = map.size() - 1; + std::vector<uint32_t> vec; + for (auto&& _ : state) { + benchmark::DoNotOptimize(map.emplace(val, vec)); + } +} +BENCHMARK(BM_treemap_emplace_same); +static void BM_treemap_try_emplace_same(benchmark::State& state) { + auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>(); + auto val = map.size() - 1; + for (auto&& _ : state) { + benchmark::DoNotOptimize(map.try_emplace(val)); + } +} +BENCHMARK(BM_treemap_try_emplace_same); +static void BM_treemap_find(benchmark::State& state) { + auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>(); + auto val = map.size() - 1; + for (auto&& _ : state) { + benchmark::DoNotOptimize(map.find(val)); + } +} +BENCHMARK(BM_treemap_find); + +static void BM_treemap_emplace_diff(benchmark::State& state) { + auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>(); + std::vector<uint32_t> vec; + auto i = map.size(); + for (auto&& _ : state) { + map.emplace(i++, vec); + } +} +BENCHMARK(BM_treemap_emplace_diff); +static void BM_treemap_try_emplace_diff(benchmark::State& state) { + auto map = prepare_map(); + auto i = map.size(); + for (auto&& _ : state) { + map.try_emplace(i++); + } +} +BENCHMARK(BM_treemap_try_emplace_diff); +static void BM_treemap_find_emplace_diff(benchmark::State& state) { + auto map = prepare_map(); + std::vector<uint32_t> vec; + auto i = map.size(); + for (auto&& _ : state) { + if (map.find(i++) == map.end()) { + map.emplace(i - 1, vec); + } + } +} +BENCHMARK(BM_treemap_find_emplace_diff); + +} // namespace android
\ No newline at end of file diff --git a/libs/androidfw/tests/ObbFile_test.cpp b/libs/androidfw/tests/ObbFile_test.cpp index 115112128636..ba818c4d7645 100644 --- a/libs/androidfw/tests/ObbFile_test.cpp +++ b/libs/androidfw/tests/ObbFile_test.cpp @@ -43,9 +43,9 @@ protected: mFileName.append(externalStorage); mFileName.append(TEST_FILENAME); - int fd = ::open(mFileName.string(), O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + int fd = ::open(mFileName.c_str(), O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd < 0) { - FAIL() << "Couldn't create " << mFileName.string() << " for tests"; + FAIL() << "Couldn't create " << mFileName.c_str() << " for tests"; } } @@ -69,17 +69,17 @@ TEST_F(ObbFileTest, WriteThenRead) { EXPECT_TRUE(mObbFile->setSalt(salt, SALT_SIZE)) << "Salt should be successfully set"; - EXPECT_TRUE(mObbFile->writeTo(mFileName.string())) + EXPECT_TRUE(mObbFile->writeTo(mFileName.c_str())) << "couldn't write to fake .obb file"; mObbFile = new ObbFile(); - EXPECT_TRUE(mObbFile->readFrom(mFileName.string())) + EXPECT_TRUE(mObbFile->readFrom(mFileName.c_str())) << "couldn't read from fake .obb file"; EXPECT_EQ(versionNum, mObbFile->getVersion()) << "version didn't come out the same as it went in"; - const char* currentPackageName = mObbFile->getPackageName().string(); + const char* currentPackageName = mObbFile->getPackageName().c_str(); EXPECT_STREQ(packageName, currentPackageName) << "package name didn't come out the same as it went in"; diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp index fbf70981f2de..faac51403203 100644 --- a/libs/androidfw/tests/ResTable_test.cpp +++ b/libs/androidfw/tests/ResTable_test.cpp @@ -64,8 +64,8 @@ TEST(ResTableTest, ResourceNameIsResolved) { String16 defPackage("com.android.basic"); String16 testName("@string/test1"); uint32_t resID = - table.identifierForName(testName.string(), testName.size(), 0, 0, - defPackage.string(), defPackage.size()); + table.identifierForName(testName.c_str(), testName.size(), 0, 0, + defPackage.c_str(), defPackage.size()); ASSERT_NE(uint32_t(0x00000000), resID); ASSERT_EQ(basic::R::string::test1, resID); } @@ -468,7 +468,7 @@ TEST_P(ResTableParameterizedTest, ShouldLoadSparseEntriesSuccessfully) { 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); + table.identifierForName(name.c_str(), name.size(), nullptr, 0, nullptr, 0, &flags); ASSERT_NE(0u, resid); Res_value val; diff --git a/libs/androidfw/tests/Split_test.cpp b/libs/androidfw/tests/Split_test.cpp index 2c242dbd3e28..3d88577c078f 100644 --- a/libs/androidfw/tests/Split_test.cpp +++ b/libs/androidfw/tests/Split_test.cpp @@ -261,8 +261,8 @@ TEST_F(SplitTest, TestNewResourceIsAccessibleByName) { const String16 package("com.android.basic"); ASSERT_EQ( R::string::test3, - table.identifierForName(name.string(), name.size(), type.string(), - type.size(), package.string(), package.size())); + table.identifierForName(name.c_str(), name.size(), type.c_str(), + type.size(), package.c_str(), package.size())); } } // namespace diff --git a/libs/androidfw/tests/TestHelpers.cpp b/libs/androidfw/tests/TestHelpers.cpp index 10c0a4fc8316..c6f657c5d9a8 100644 --- a/libs/androidfw/tests/TestHelpers.cpp +++ b/libs/androidfw/tests/TestHelpers.cpp @@ -79,9 +79,9 @@ AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id, } if (String8(expected_str) != *actual_str) { - return AssertionFailure() << actual_str->string(); + return AssertionFailure() << actual_str->c_str(); } - return AssertionSuccess() << actual_str->string(); + return AssertionSuccess() << actual_str->c_str(); } } // namespace android diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp index e08a6a7f277d..181d1411fb91 100644 --- a/libs/androidfw/tests/Theme_test.cpp +++ b/libs/androidfw/tests/Theme_test.cpp @@ -260,7 +260,7 @@ TEST_F(ThemeTest, ThemeRebase) { ResTable_config night{}; night.uiMode = ResTable_config::UI_MODE_NIGHT_YES; night.version = 8u; - am_night.SetConfiguration(night); + am_night.SetConfigurations({night}); auto theme = am.NewTheme(); { diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt index 96bfb78eff0d..d1dd8df81a3e 100644 --- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt +++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt @@ -23,6 +23,7 @@ import android.content.ComponentName import android.util.Log import com.android.dream.lowlight.dagger.LowLightDreamModule import com.android.dream.lowlight.dagger.qualifiers.Application +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.TimeoutCancellationException @@ -103,6 +104,11 @@ class LowLightDreamManager @Inject constructor( ) } catch (ex: TimeoutCancellationException) { Log.e(TAG, "timed out while waiting for low light animation", ex) + } catch (ex: CancellationException) { + Log.w(TAG, "low light transition animation cancelled") + // Catch the cancellation so that we still set the system dream component if the + // animation is cancelled, such as by a user tapping to wake as the transition to + // low light happens. } dreamManager.setSystemDreamComponent( if (shouldEnterLowLight) lowLightDreamComponent else null diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt index 473603002b21..de1aee598667 100644 --- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt +++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt @@ -110,15 +110,5 @@ class LowLightTransitionCoordinator @Inject constructor() { } } animator.addListener(listener) - continuation.invokeOnCancellation { - try { - animator.removeListener(listener) - animator.cancel() - } catch (exception: IndexOutOfBoundsException) { - // TODO(b/285666217): remove this try/catch once a proper fix is implemented. - // Cancelling the animator can cause an exception since we may be removing a - // listener during the cancellation. See b/285666217 for more details. - } - } } } diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt new file mode 100644 index 000000000000..f69c84dafbb2 --- /dev/null +++ b/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt @@ -0,0 +1,54 @@ +/* + * 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.dream.lowlight.util + +import android.view.animation.Interpolator + +/** + * Interpolator wrapper that shortens another interpolator from its original duration to a portion + * of that duration. + * + * For example, an `originalDuration` of 1000 and a `newDuration` of 200 results in an animation + * that when played for 200ms is the exact same as the first 200ms of a 1000ms animation if using + * the original interpolator. + * + * This is useful for the transition between the user dream and the low light clock as some + * animations are defined in the spec to be longer than the total duration of the animation. For + * example, the low light clock exit translation animation is defined to last >1s while the actual + * fade out of the low light clock is only 250ms, meaning the clock isn't visible anymore after + * 250ms. + * + * Since the dream framework currently only allows one dream to be visible and running, we use this + * interpolator to play just the first 250ms of the translation animation. Simply reducing the + * duration of the animation would result in the text exiting much faster than intended, so a custom + * interpolator is needed. + */ +class TruncatedInterpolator( + private val baseInterpolator: Interpolator, + originalDuration: Float, + newDuration: Float +) : Interpolator { + private val scaleFactor: Float + + init { + scaleFactor = newDuration / originalDuration + } + + override fun getInterpolation(input: Float): Float { + return baseInterpolator.getInterpolation(input * scaleFactor) + } +} diff --git a/libs/dream/lowlight/tests/Android.bp b/libs/dream/lowlight/tests/Android.bp index 2d79090cd7d4..64b53cbb5c5a 100644 --- a/libs/dream/lowlight/tests/Android.bp +++ b/libs/dream/lowlight/tests/Android.bp @@ -27,6 +27,7 @@ android_test { "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", + "animationlib", "frameworks-base-testutils", "junit", "kotlinx_coroutines_test", diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt index 2a886bc31788..de84adb2e5c2 100644 --- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt +++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt @@ -152,6 +152,21 @@ class LowLightDreamManagerTest { verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT) } + @Test + fun setAmbientLightMode_animationCancelled_SetsSystemDream() = testScope.runTest { + mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT) + runCurrent() + cancelEnterAnimations() + runCurrent() + // Animation never finishes, but we should still set the system dream + verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT) + } + + private fun cancelEnterAnimations() { + val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) } + listener.onAnimationCancel(mEnterAnimator) + } + private fun completeEnterAnimations() { val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) } listener.onAnimationEnd(mEnterAnimator) diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt index 4c526a6ac69d..9ae304f9763a 100644 --- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt +++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt @@ -158,26 +158,6 @@ class LowLightTransitionCoordinatorTest { assertThat(job.isCancelled).isTrue() } - @Test - fun shouldCancelAnimatorWhenJobCancelled() = testScope.runTest { - whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator) - val coordinator = LowLightTransitionCoordinator() - coordinator.setLowLightEnterListener(mEnterListener) - val job = launch { - coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true) - } - runCurrent() - // Animator listener is added and the runnable is not run yet. - verify(mAnimator).addListener(mAnimatorListenerCaptor.capture()) - verify(mAnimator, never()).cancel() - assertThat(job.isCompleted).isFalse() - - job.cancel() - // We should have removed the listener and cancelled the animator - verify(mAnimator).removeListener(mAnimatorListenerCaptor.value) - verify(mAnimator).cancel() - } - companion object { private val TIMEOUT = 1.toDuration(DurationUnit.SECONDS) } diff --git a/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt new file mode 100644 index 000000000000..190f02e97136 --- /dev/null +++ b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt @@ -0,0 +1,53 @@ +/* + * 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.dream.lowlight.util + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.app.animation.Interpolators +import com.google.common.truth.Truth +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class TruncatedInterpolatorTest { + @Test + fun truncatedInterpolator_matchesRegularInterpolator() { + val originalInterpolator = Interpolators.EMPHASIZED + val truncatedInterpolator = + TruncatedInterpolator(originalInterpolator, ORIGINAL_DURATION_MS, NEW_DURATION_MS) + + // Both interpolators should start at the same value. + var animationPercent = 0f + Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent)) + .isEqualTo(originalInterpolator.getInterpolation(animationPercent)) + + animationPercent = 1f + Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent)) + .isEqualTo(originalInterpolator.getInterpolation(animationPercent * DURATION_RATIO)) + + animationPercent = 0.25f + Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent)) + .isEqualTo(originalInterpolator.getInterpolation(animationPercent * DURATION_RATIO)) + } + + companion object { + private const val ORIGINAL_DURATION_MS: Float = 1000f + private const val NEW_DURATION_MS: Float = 200f + private const val DURATION_RATIO: Float = NEW_DURATION_MS / ORIGINAL_DURATION_MS + } +} diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index db581471e2ca..502ff874ea69 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -28,6 +28,24 @@ license { ], } +aconfig_declarations { + name: "hwui_flags", + package: "com.android.graphics.hwui.flags", + srcs: [ + "aconfig/hwui_flags.aconfig", + ], +} + +java_aconfig_library { + name: "hwui_flags_java_lib", + aconfig_declarations: "hwui_flags", +} + +cc_aconfig_library { + name: "hwui_flags_cc_lib", + aconfig_declarations: "hwui_flags", +} + cc_defaults { name: "hwui_defaults", defaults: [ @@ -44,7 +62,7 @@ cc_defaults { "-DEGL_EGLEXT_PROTOTYPES", "-DGL_GLEXT_PROTOTYPES", "-DATRACE_TAG=ATRACE_TAG_VIEW", - "-DLOG_TAG=\"OpenGLRenderer\"", + "-DLOG_TAG=\"HWUI\"", "-Wall", "-Wthread-safety", "-Wno-unused-parameter", @@ -139,6 +157,7 @@ cc_defaults { "libstatspull_lazy", "libstatssocket_lazy", "libtonemap", + "hwui_flags_cc_lib", ], }, host: { @@ -514,6 +533,7 @@ cc_defaults { "canvas/CanvasOpRasterizer.cpp", "effects/StretchEffect.cpp", "effects/GainmapRenderer.cpp", + "pipeline/skia/BackdropFilterDrawable.cpp", "pipeline/skia/HolePunch.cpp", "pipeline/skia/SkiaDisplayList.cpp", "pipeline/skia/SkiaRecordingCanvas.cpp", @@ -717,6 +737,7 @@ cc_test { "tests/unit/EglManagerTests.cpp", "tests/unit/FatVectorTests.cpp", "tests/unit/GraphicsStatsServiceTests.cpp", + "tests/unit/HintSessionWrapperTests.cpp", "tests/unit/JankTrackerTests.cpp", "tests/unit/FrameMetricsReporterTests.cpp", "tests/unit/LayerUpdateQueueTests.cpp", diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp index b656b6ac8204..4d020c567972 100644 --- a/libs/hwui/AutoBackendTextureRelease.cpp +++ b/libs/hwui/AutoBackendTextureRelease.cpp @@ -16,6 +16,11 @@ #include "AutoBackendTextureRelease.h" +#include <SkImage.h> +#include <include/gpu/ganesh/SkImageGanesh.h> +#include <include/gpu/GrDirectContext.h> +#include <include/gpu/GrBackendSurface.h> +#include <include/gpu/MutableTextureState.h> #include "renderthread/RenderThread.h" #include "utils/Color.h" #include "utils/PaintUtils.h" @@ -30,15 +35,47 @@ AutoBackendTextureRelease::AutoBackendTextureRelease(GrDirectContext* context, AHardwareBuffer_Desc desc; AHardwareBuffer_describe(buffer, &desc); bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT); - GrBackendFormat backendFormat = - GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false); + + GrBackendFormat backendFormat; + GrBackendApi backend = context->backend(); + if (backend == GrBackendApi::kOpenGL) { + backendFormat = + GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false); + mBackendTexture = + GrAHardwareBufferUtils::MakeGLBackendTexture(context, + buffer, + desc.width, + desc.height, + &mDeleteProc, + &mUpdateProc, + &mImageCtx, + createProtectedImage, + backendFormat, + false); + } else if (backend == GrBackendApi::kVulkan) { + backendFormat = + GrAHardwareBufferUtils::GetVulkanBackendFormat(context, + buffer, + desc.format, + false); + mBackendTexture = + GrAHardwareBufferUtils::MakeVulkanBackendTexture(context, + buffer, + desc.width, + desc.height, + &mDeleteProc, + &mUpdateProc, + &mImageCtx, + createProtectedImage, + backendFormat, + false); + } else { + LOG_ALWAYS_FATAL("Unexpected backend %d", backend); + } LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(), __FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32 ", AHardwareBuffer_Format==%" PRIu32 ".", static_cast<int>(context->backend()), desc.format); - mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture( - context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx, - createProtectedImage, backendFormat, false); LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(), __FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32 ", protected==%d", @@ -70,7 +107,7 @@ void AutoBackendTextureRelease::unref(bool releaseImage) { // releaseProc is invoked by SkImage, when texture is no longer in use. // "releaseContext" contains an "AutoBackendTextureRelease*". -static void releaseProc(SkImage::ReleaseContext releaseContext) { +static void releaseProc(SkImages::ReleaseContext releaseContext) { AutoBackendTextureRelease* textureRelease = reinterpret_cast<AutoBackendTextureRelease*>(releaseContext); textureRelease->unref(false); @@ -83,10 +120,10 @@ void AutoBackendTextureRelease::makeImage(AHardwareBuffer* buffer, AHardwareBuffer_describe(buffer, &desc); SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format); // The following ref will be counteracted by Skia calling releaseProc, either during - // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must - // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure. + // BorrowTextureFrom if there is a failure, or later when SkImage is discarded. It must + // be called before BorrowTextureFrom, otherwise Skia may remove HWUI's ref on failure. ref(); - mImage = SkImage::MakeFromTexture( + mImage = SkImages::BorrowTextureFrom( context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType, uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this); } @@ -105,8 +142,8 @@ void AutoBackendTextureRelease::releaseQueueOwnership(GrDirectContext* context) LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan); if (mBackendTexture.isValid()) { // Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout. - GrBackendSurfaceMutableState newState(VK_IMAGE_LAYOUT_UNDEFINED, - VK_QUEUE_FAMILY_FOREIGN_EXT); + skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED, + VK_QUEUE_FAMILY_FOREIGN_EXT); // The unref for this ref happens in the releaseProc passed into setBackendTextureState. The // releaseProc callback will be made when the work to set the new state has finished on the diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h new file mode 100644 index 000000000000..1a5b938d6eed --- /dev/null +++ b/libs/hwui/ColorFilter.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. + */ + +#ifndef COLORFILTER_H_ +#define COLORFILTER_H_ + +#include <stdint.h> + +#include <memory> + +#include "GraphicsJNI.h" +#include "SkColorFilter.h" +#include "SkiaWrapper.h" + +namespace android { +namespace uirenderer { + +class ColorFilter : public SkiaWrapper<SkColorFilter> { +public: + static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); } + +protected: + ColorFilter() = default; +}; + +class BlendModeColorFilter : public ColorFilter { +public: + BlendModeColorFilter(SkColor color, SkBlendMode mode) : mColor(color), mMode(mode) {} + +private: + sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Blend(mColor, mMode); } + +private: + const SkColor mColor; + const SkBlendMode mMode; +}; + +class LightingFilter : public ColorFilter { +public: + LightingFilter(SkColor mul, SkColor add) : mMul(mul), mAdd(add) {} + + void setMul(SkColor mul) { + mMul = mul; + discardInstance(); + } + + void setAdd(SkColor add) { + mAdd = add; + discardInstance(); + } + +private: + sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Lighting(mMul, mAdd); } + +private: + SkColor mMul; + SkColor mAdd; +}; + +class ColorMatrixColorFilter : public ColorFilter { +public: + ColorMatrixColorFilter(std::vector<float>&& matrix) : mMatrix(std::move(matrix)) {} + + void setMatrix(std::vector<float>&& matrix) { + mMatrix = std::move(matrix); + discardInstance(); + } + +private: + sk_sp<SkColorFilter> createInstance() override { + return SkColorFilters::Matrix(mMatrix.data()); + } + +private: + std::vector<float> mMatrix; +}; + +} // namespace uirenderer +} // namespace android + +#endif // COLORFILTER_H_ diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index eb5878d95561..8c180da9c84f 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -54,6 +54,8 @@ public: mImpl->updateChildren(std::move(updateFn)); } + void visit(std::function<void(const RenderNode&)> func) const { mImpl->visit(std::move(func)); } + [[nodiscard]] explicit operator bool() const { return mImpl.get() != nullptr; } diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in index a18ba1c633b9..d21f07efe36a 100644 --- a/libs/hwui/DisplayListOps.in +++ b/libs/hwui/DisplayListOps.in @@ -14,7 +14,6 @@ * limitations under the License. */ -X(Flush) X(Save) X(Restore) X(SaveLayer) diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp index 8191f5e6a83a..a958a091a830 100644 --- a/libs/hwui/FrameInfo.cpp +++ b/libs/hwui/FrameInfo.cpp @@ -15,6 +15,8 @@ */ #include "FrameInfo.h" +#include <gui/TraceUtils.h> + #include <cstring> namespace android { @@ -51,6 +53,30 @@ static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 23, void FrameInfo::importUiThreadInfo(int64_t* info) { memcpy(mFrameInfo, info, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t)); + mSkippedFrameReason.reset(); +} + +const char* toString(SkippedFrameReason reason) { + switch (reason) { + case SkippedFrameReason::DrawingOff: + return "DrawingOff"; + case SkippedFrameReason::ContextIsStopped: + return "ContextIsStopped"; + case SkippedFrameReason::NothingToDraw: + return "NothingToDraw"; + case SkippedFrameReason::NoOutputTarget: + return "NoOutputTarget"; + case SkippedFrameReason::NoBuffer: + return "NoBuffer"; + case SkippedFrameReason::AlreadyDrawn: + return "AlreadyDrawn"; + } +} + +void FrameInfo::setSkippedFrameReason(android::uirenderer::SkippedFrameReason reason) { + ATRACE_FORMAT_INSTANT("Frame skipped: %s", toString(reason)); + addFlag(FrameInfoFlags::SkippedFrame); + mSkippedFrameReason = reason; } } /* namespace uirenderer */ diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h index b15b6cb9a9ec..f7ad13978a30 100644 --- a/libs/hwui/FrameInfo.h +++ b/libs/hwui/FrameInfo.h @@ -16,15 +16,17 @@ #ifndef FRAMEINFO_H_ #define FRAMEINFO_H_ -#include "utils/Macros.h" - #include <cutils/compiler.h> +#include <memory.h> #include <utils/Timers.h> #include <array> -#include <memory.h> +#include <optional> #include <string> +#include "SkippedFrameInfo.h" +#include "utils/Macros.h" + namespace android { namespace uirenderer { @@ -186,8 +188,14 @@ public: return mFrameInfo[static_cast<int>(index)]; } + void setSkippedFrameReason(SkippedFrameReason reason); + inline std::optional<SkippedFrameReason> getSkippedFrameReason() const { + return mSkippedFrameReason; + } + private: int64_t mFrameInfo[static_cast<int>(FrameInfoIndex::NumIndexes)]; + std::optional<SkippedFrameReason> mSkippedFrameReason; }; } /* namespace uirenderer */ diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp index 687e4dd324d3..59f21694fb77 100644 --- a/libs/hwui/FrameInfoVisualizer.cpp +++ b/libs/hwui/FrameInfoVisualizer.cpp @@ -148,7 +148,7 @@ void FrameInfoVisualizer::initializeRects(const int baseline, const int width) { int fast_i = 0, janky_i = 0; // Set the bottom of all the shapes to the baseline for (int fi = mFrameSource.size() - 1; fi >= 0; fi--) { - if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) { + if (mFrameSource[fi].getSkippedFrameReason()) { continue; } float lineWidth = baseLineWidth; @@ -181,7 +181,7 @@ void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex en int janky_i = (mNumJankyRects - 1) * 4; for (size_t fi = 0; fi < mFrameSource.size(); fi++) { - if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) { + if (mFrameSource[fi].getSkippedFrameReason()) { continue; } diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index b7e99994355c..16de21def0e3 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -22,9 +22,11 @@ #include <GLES2/gl2ext.h> #include <GLES3/gl3.h> #include <GrDirectContext.h> +#include <GrTypes.h> #include <SkBitmap.h> #include <SkCanvas.h> #include <SkImage.h> +#include <SkImageAndroid.h> #include <SkImageInfo.h> #include <SkRefCnt.h> #include <gui/TraceUtils.h> @@ -262,8 +264,9 @@ private: } sk_sp<SkImage> image = - SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), ahb); - mGrContext->submit(true); + SkImages::TextureFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), + ahb); + mGrContext->submit(GrSyncCpu::kYes); uploadSucceeded = (image.get() != nullptr); }); diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp index ca1312e75f4c..21f4ca79b68c 100644 --- a/libs/hwui/MemoryPolicy.cpp +++ b/libs/hwui/MemoryPolicy.cpp @@ -28,7 +28,10 @@ namespace android::uirenderer { constexpr static MemoryPolicy sDefaultMemoryPolicy; constexpr static MemoryPolicy sPersistentOrSystemPolicy{ .contextTimeout = 10_s, + .minimumResourceRetention = 1_s, + .maximumResourceRetention = 10_s, .useAlternativeUiHidden = true, + .purgeScratchOnly = false, }; constexpr static MemoryPolicy sLowRamPolicy{ .useAlternativeUiHidden = true, diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h index 347daf34f52a..e10dda990dec 100644 --- a/libs/hwui/MemoryPolicy.h +++ b/libs/hwui/MemoryPolicy.h @@ -53,6 +53,8 @@ struct MemoryPolicy { // 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; + // The maximum amount of time to hold onto items in the resource cache + nsecs_t maximumResourceRetention = 100000_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; diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h index 13e3c8e7bf77..764d1efcc8f4 100644 --- a/libs/hwui/Mesh.h +++ b/libs/hwui/Mesh.h @@ -19,6 +19,7 @@ #include <GrDirectContext.h> #include <SkMesh.h> +#include <include/gpu/ganesh/SkMeshGanesh.h> #include <jni.h> #include <log/log.h> @@ -143,14 +144,26 @@ public: } if (mIsDirty || genId != mGenerationId) { - auto vb = SkMesh::MakeVertexBuffer( - context, reinterpret_cast<const void*>(mVertexBufferData.data()), - mVertexBufferData.size()); + auto vertexData = reinterpret_cast<const void*>(mVertexBufferData.data()); +#ifdef __ANDROID__ + auto vb = SkMeshes::MakeVertexBuffer(context, + vertexData, + mVertexBufferData.size()); +#else + auto vb = SkMeshes::MakeVertexBuffer(vertexData, + mVertexBufferData.size()); +#endif auto meshMode = SkMesh::Mode(mMode); if (!mIndexBufferData.empty()) { - auto ib = SkMesh::MakeIndexBuffer( - context, reinterpret_cast<const void*>(mIndexBufferData.data()), - mIndexBufferData.size()); + auto indexData = reinterpret_cast<const void*>(mIndexBufferData.data()); +#ifdef __ANDROID__ + auto ib = SkMeshes::MakeIndexBuffer(context, + indexData, + mIndexBufferData.size()); +#else + auto ib = SkMeshes::MakeIndexBuffer(indexData, + mIndexBufferData.size()); +#endif mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset, ib, mIndexCount, mIndexOffset, mBuilder->fUniforms, mBounds) diff --git a/libs/hwui/OWNERS b/libs/hwui/OWNERS index bb93e66968be..6ca991d8b294 100644 --- a/libs/hwui/OWNERS +++ b/libs/hwui/OWNERS @@ -1,3 +1,5 @@ +# Bug component: 1075005 + alecmouri@google.com djsollen@google.com jreck@google.com diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 06aed63d8def..5e5eb4a51b35 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -20,7 +20,7 @@ #ifdef __ANDROID__ #include "HWUIProperties.sysprop.h" #endif -#include "SkTraceEventCommon.h" +#include "src/core/SkTraceEventCommon.h" #include <algorithm> #include <cstdlib> diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index 045de35c1d97..afe4c3896ed2 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -21,6 +21,7 @@ #include <SkCanvas.h> #include <SkColorSpace.h> #include <SkImage.h> +#include <SkImageAndroid.h> #include <SkImageInfo.h> #include <SkMatrix.h> #include <SkPaint.h> @@ -29,6 +30,7 @@ #include <SkSamplingOptions.h> #include <SkSurface.h> #include "include/gpu/GpuTypes.h" // from Skia +#include <include/gpu/ganesh/SkSurfaceGanesh.h> #include <gui/TraceUtils.h> #include <private/android/AHardwareBufferHelpers.h> #include <shaders/shaders.h> @@ -108,7 +110,8 @@ void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<Copy sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace)); sk_sp<SkImage> image = - SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace); + SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, + colorSpace); if (!image.get()) { return request->onCopyFinished(CopyResult::UnknownError); @@ -171,16 +174,16 @@ void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<Copy SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height()); SkBitmap* bitmap = &skBitmap; sk_sp<SkSurface> tmpSurface = - SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes, - bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr); + SkSurfaces::RenderTarget(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(), - skgpu::Budgeted::kYes, - tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr); + tmpSurface = SkSurfaces::RenderTarget(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 request->onCopyFinished(CopyResult::UnknownError); @@ -346,19 +349,19 @@ bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* * a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a * software buffer. */ - sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), - skgpu::Budgeted::kYes, - bitmap->info(), - 0, - kTopLeft_GrSurfaceOrigin, nullptr); + sk_sp<SkSurface> tmpSurface = SkSurfaces::RenderTarget(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(), - skgpu::Budgeted::kYes, - tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr); + tmpSurface = SkSurfaces::RenderTarget(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 false; diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 924fbd659824..71f47e92e055 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -36,6 +36,7 @@ #include "SkImageFilter.h" #include "SkImageInfo.h" #include "SkLatticeIter.h" +#include "SkMesh.h" #include "SkPaint.h" #include "SkPicture.h" #include "SkRRect.h" @@ -49,6 +50,7 @@ #include "effects/GainmapRenderer.h" #include "include/gpu/GpuTypes.h" // from Skia #include "include/gpu/GrDirectContext.h" +#include "include/gpu/ganesh/SkMeshGanesh.h" #include "pipeline/skia/AnimatedDrawables.h" #include "pipeline/skia/FunctorDrawable.h" #ifdef __ANDROID__ @@ -107,11 +109,6 @@ struct Op { }; static_assert(sizeof(Op) == 4, ""); -struct Flush final : Op { - static const auto kType = Type::Flush; - void draw(SkCanvas* c, const SkMatrix&) const { c->flush(); } -}; - struct Save final : Op { static const auto kType = Type::Save; void draw(SkCanvas* c, const SkMatrix&) const { c->save(); } @@ -532,12 +529,13 @@ struct DrawSkMesh final : Op { mutable bool isGpuBased; mutable GrDirectContext::DirectContextID contextId; void draw(SkCanvas* c, const SkMatrix&) const { +#ifdef __ANDROID__ GrDirectContext* directContext = c->recordingContext()->asDirectContext(); GrDirectContext::DirectContextID id = directContext->directContextID(); if (!isGpuBased || contextId != id) { sk_sp<SkMesh::VertexBuffer> vb = - SkMesh::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer()); + SkMeshes::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer()); if (!cpuMesh.indexBuffer()) { gpuMesh = SkMesh::Make(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(), cpuMesh.vertexOffset(), cpuMesh.refUniforms(), @@ -545,7 +543,7 @@ struct DrawSkMesh final : Op { .mesh; } else { sk_sp<SkMesh::IndexBuffer> ib = - SkMesh::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer()); + SkMeshes::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer()); gpuMesh = SkMesh::MakeIndexed(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(), cpuMesh.vertexOffset(), ib, cpuMesh.indexCount(), cpuMesh.indexOffset(), @@ -558,6 +556,9 @@ struct DrawSkMesh final : Op { } c->drawMesh(gpuMesh, blender, paint); +#else + c->drawMesh(cpuMesh, blender, paint); +#endif } }; @@ -675,12 +676,11 @@ public: // because the webview functor still doesn't respect the canvas clip stack. const SkIRect deviceBounds = c->getDeviceClipBounds(); if (mLayerSurface == nullptr || c->imageInfo() != mLayerImageInfo) { - GrRecordingContext* directContext = c->recordingContext(); mLayerImageInfo = c->imageInfo().makeWH(deviceBounds.width(), deviceBounds.height()); - mLayerSurface = SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes, - mLayerImageInfo, 0, - kTopLeft_GrSurfaceOrigin, nullptr); + // SkCanvas::makeSurface returns a new surface that will be GPU-backed if + // canvas was also. + mLayerSurface = c->makeSurface(mLayerImageInfo); } SkCanvas* layerCanvas = mLayerSurface->getCanvas(); @@ -752,10 +752,6 @@ inline void DisplayListData::map(const Fn fns[], Args... args) const { } } -void DisplayListData::flush() { - this->push<Flush>(0); -} - void DisplayListData::save() { this->push<Save>(0); } @@ -1047,10 +1043,6 @@ sk_sp<SkSurface> RecordingCanvas::onNewSurface(const SkImageInfo&, const SkSurfa return nullptr; } -void RecordingCanvas::onFlush() { - fDL->flush(); -} - void RecordingCanvas::willSave() { mSaveCount++; fDL->save(); diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 1f4ba5d6d557..4f54ee286a56 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -127,8 +127,6 @@ public: private: friend class RecordingCanvas; - void flush(); - void save(); void saveLayer(const SkRect*, const SkPaint*, const SkImageFilter*, SkCanvas::SaveLayerFlags); void saveBehind(const SkRect*); @@ -208,8 +206,6 @@ public: void willRestore() override; bool onDoSaveBehind(const SkRect*) override; - void onFlush() override; - void didConcat44(const SkM44&) override; void didSetM44(const SkM44&) override; void didScale(SkScalar, SkScalar) override; diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 1c39db3a31bb..d28bb499c907 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -28,16 +28,20 @@ #include "DamageAccumulator.h" #include "pipeline/skia/SkiaDisplayList.h" #endif +#include <SkPathOps.h> #include <gui/TraceUtils.h> -#include "utils/MathUtils.h" -#include "utils/StringUtils.h" +#include <ui/FatVector.h> -#include <SkPathOps.h> #include <algorithm> #include <atomic> #include <sstream> #include <string> -#include <ui/FatVector.h> + +#ifdef __ANDROID__ +#include "include/gpu/ganesh/SkImageGanesh.h" +#endif +#include "utils/MathUtils.h" +#include "utils/StringUtils.h" namespace android { namespace uirenderer { @@ -109,6 +113,13 @@ void RenderNode::output(std::ostream& output, uint32_t level) { output << std::endl; } +void RenderNode::visit(std::function<void(const RenderNode&)> func) const { + func(*this); + if (mDisplayList) { + mDisplayList.visit(func); + } +} + int RenderNode::getUsageSize() { int size = sizeof(RenderNode); size += mStagingDisplayList.getUsedSize(); @@ -260,6 +271,12 @@ void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool fu pushStagingDisplayListChanges(observer, info); } + // always damageSelf when filtering backdrop content, or else the BackdropFilterDrawable will + // get a wrong snapshot of previous content. + if (mProperties.layerProperties().getBackdropImageFilter()) { + damageSelf(info); + } + if (mDisplayList) { info.out.hasFunctors |= mDisplayList.hasFunctor(); mHasHolePunches = mDisplayList.hasHolePunches(); @@ -357,13 +374,18 @@ std::optional<RenderNode::SnapshotResult> RenderNode::updateSnapshotIfRequired( mImageFilterClipBounds != clipBounds || mTargetImageFilterLayerSurfaceGenerationId != layerSurfaceGenerationId) { // Otherwise create a new snapshot with the given filter and snapshot - mSnapshotResult.snapshot = - snapshot->makeWithFilter(context, - imageFilter, - subset, - clipBounds, - &mSnapshotResult.outSubset, - &mSnapshotResult.outOffset); +#ifdef __ANDROID__ + if (context) { + mSnapshotResult.snapshot = SkImages::MakeWithFilter( + context, snapshot, imageFilter, subset, clipBounds, &mSnapshotResult.outSubset, + &mSnapshotResult.outOffset); + } else +#endif + { + mSnapshotResult.snapshot = SkImages::MakeWithFilter( + snapshot, imageFilter, subset, clipBounds, &mSnapshotResult.outSubset, + &mSnapshotResult.outOffset); + } mTargetImageFilter = sk_ref_sp(imageFilter); mImageFilterClipBounds = clipBounds; mTargetImageFilterLayerSurfaceGenerationId = layerSurfaceGenerationId; diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index d1e04adcb642..c959db37474b 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -114,25 +114,21 @@ public: return mDisplayList.containsProjectionReceiver(); } - const char* getName() const { return mName.string(); } + const char* getName() const { return mName.c_str(); } void setName(const char* name) { if (name) { const char* lastPeriod = strrchr(name, '.'); if (lastPeriod) { - mName.setTo(lastPeriod + 1); + mName = (lastPeriod + 1); } else { - mName.setTo(name); + mName = name; } } } StretchMask& getStretchMask() { return mStretchMask; } - VirtualLightRefBase* getUserContext() const { return mUserContext.get(); } - - void setUserContext(VirtualLightRefBase* context) { mUserContext = context; } - bool isPropertyFieldDirty(DirtyPropertyMask field) const { return mDirtyPropertyFields & field; } @@ -215,6 +211,8 @@ public: void output(std::ostream& output, uint32_t level); + void visit(std::function<void(const RenderNode&)>) const; + void setUsageHint(UsageHint usageHint) { mUsageHint = usageHint; } UsageHint usageHint() const { return mUsageHint; } @@ -222,6 +220,7 @@ public: int64_t uniqueId() const { return mUniqueId; } void setIsTextureView() { mIsTextureView = true; } + bool isTextureView() const { return mIsTextureView; } void markDrawStart(SkCanvas& canvas); void markDrawEnd(SkCanvas& canvas); @@ -248,7 +247,6 @@ private: const int64_t mUniqueId; String8 mName; - sp<VirtualLightRefBase> mUserContext; uint32_t mDirtyPropertyFields; RenderProperties mProperties; diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp index 0589f136b666..c5371236b9cf 100644 --- a/libs/hwui/RenderProperties.cpp +++ b/libs/hwui/RenderProperties.cpp @@ -55,6 +55,12 @@ bool LayerProperties::setImageFilter(SkImageFilter* imageFilter) { return true; } +bool LayerProperties::setBackdropImageFilter(SkImageFilter* imageFilter) { + if (mBackdropImageFilter.get() == imageFilter) return false; + mBackdropImageFilter = sk_ref_sp(imageFilter); + return true; +} + bool LayerProperties::setFromPaint(const SkPaint* paint) { bool changed = false; changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint))); @@ -70,6 +76,7 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) { setXferMode(other.xferMode()); setColorFilter(other.getColorFilter()); setImageFilter(other.getImageFilter()); + setBackdropImageFilter(other.getBackdropImageFilter()); mStretchEffect = other.mStretchEffect; return *this; } diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 064ba7aee107..e358b57f6fe1 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -97,8 +97,12 @@ public: bool setImageFilter(SkImageFilter* imageFilter); + bool setBackdropImageFilter(SkImageFilter* imageFilter); + SkImageFilter* getImageFilter() const { return mImageFilter.get(); } + SkImageFilter* getBackdropImageFilter() const { return mBackdropImageFilter.get(); } + const StretchEffect& getStretchEffect() const { return mStretchEffect; } StretchEffect& mutableStretchEffect() { return mStretchEffect; } @@ -129,6 +133,7 @@ private: SkBlendMode mMode; sk_sp<SkColorFilter> mColorFilter; sk_sp<SkImageFilter> mImageFilter; + sk_sp<SkImageFilter> mBackdropImageFilter; StretchEffect mStretchEffect; }; diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index b785989f35cb..ced02241ffe2 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -175,7 +175,7 @@ protected: const Paint& paint, const SkPath& path, size_t start, size_t end) override; - void onFilterPaint(Paint& paint); + virtual void onFilterPaint(Paint& paint); Paint filterPaint(const Paint& src) { Paint dst(src); diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp index b58f517834a3..c67b135855f7 100644 --- a/libs/hwui/SkiaInterpolator.cpp +++ b/libs/hwui/SkiaInterpolator.cpp @@ -18,9 +18,8 @@ #include "include/core/SkScalar.h" #include "include/core/SkTypes.h" -#include "include/private/SkFixed.h" -#include "src/core/SkTSearch.h" +#include <cstdlib> #include <log/log.h> typedef int Dot14; @@ -41,18 +40,18 @@ static inline Dot14 pin_and_convert(float x) { if (x <= 0) { return 0; } - if (x >= SK_Scalar1) { + if (x >= 1.0f) { return Dot14_ONE; } - return SkScalarToFixed(x) >> 2; + return static_cast<Dot14>(x * Dot14_ONE); } static float SkUnitCubicInterp(float value, float bx, float by, float cx, float cy) { // pin to the unit-square, and convert to 2.14 Dot14 x = pin_and_convert(value); - if (x == 0) return 0; - if (x == Dot14_ONE) return SK_Scalar1; + if (x == 0) return 0.0f; + if (x == Dot14_ONE) return 1.0f; Dot14 b = pin_and_convert(bx); Dot14 c = pin_and_convert(cx); @@ -84,7 +83,7 @@ static float SkUnitCubicInterp(float value, float bx, float by, float cx, float A = 3 * b; B = 3 * (c - 2 * b); C = 3 * (b - c) + Dot14_ONE; - return SkFixedToScalar(eval_cubic(t, A, B, C) << 2); + return Dot14ToFloat(eval_cubic(t, A, B, C)); } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -104,7 +103,7 @@ void SkiaInterpolatorBase::reset(int elemCount, int frameCount) { fFlags = 0; fElemCount = static_cast<uint8_t>(elemCount); fFrameCount = static_cast<int16_t>(frameCount); - fRepeat = SK_Scalar1; + fRepeat = 1.0f; if (fStorage) { free(fStorage); fStorage = nullptr; @@ -136,17 +135,46 @@ bool SkiaInterpolatorBase::getDuration(SkMSec* startTime, SkMSec* endTime) const float SkiaInterpolatorBase::ComputeRelativeT(SkMSec time, SkMSec prevTime, SkMSec nextTime, const float blend[4]) { - SkASSERT(time > prevTime && time < nextTime); + LOG_FATAL_IF(time < prevTime || time > nextTime); float t = (float)(time - prevTime) / (float)(nextTime - prevTime); return blend ? SkUnitCubicInterp(t, blend[0], blend[1], blend[2], blend[3]) : t; } +// Returns the index of where the item is or the bit not of the index +// where the item should go in order to keep arr sorted in ascending order. +int SkiaInterpolatorBase::binarySearch(const SkTimeCode* arr, int count, SkMSec target) { + if (count <= 0) { + return ~0; + } + + int lo = 0; + int hi = count - 1; + + while (lo < hi) { + int mid = (hi + lo) / 2; + SkMSec elem = arr[mid].fTime; + if (elem == target) { + return mid; + } else if (elem < target) { + lo = mid + 1; + } else { + hi = mid; + } + } + // Check to see if target is greater or less than where we stopped + if (target < arr[lo].fTime) { + return ~lo; + } + // e.g. it should go at the end. + return ~(lo + 1); +} + SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T, int* indexPtr, bool* exactPtr) const { - SkASSERT(fFrameCount > 0); + LOG_FATAL_IF(fFrameCount <= 0); Result result = kNormal_Result; - if (fRepeat != SK_Scalar1) { + if (fRepeat != 1.0f) { SkMSec startTime = 0, endTime = 0; // initialize to avoid warning this->getDuration(&startTime, &endTime); SkMSec totalTime = endTime - startTime; @@ -168,10 +196,8 @@ SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T time = offsetTime + startTime; } - int index = SkTSearch<SkMSec>(&fTimes[0].fTime, fFrameCount, time, sizeof(SkTimeCode)); - + int index = SkiaInterpolatorBase::binarySearch(fTimes, fFrameCount, time); bool exact = true; - if (index < 0) { index = ~index; if (index == 0) { @@ -184,10 +210,11 @@ SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T } result = kFreezeEnd_Result; } else { + // Need to interpolate between two frames. exact = false; } } - SkASSERT(index < fFrameCount); + LOG_FATAL_IF(index >= fFrameCount); const SkTimeCode* nextTime = &fTimes[index]; SkMSec nextT = nextTime[0].fTime; if (exact) { @@ -207,7 +234,7 @@ SkiaInterpolator::SkiaInterpolator() { } SkiaInterpolator::SkiaInterpolator(int elemCount, int frameCount) { - SkASSERT(elemCount > 0); + LOG_FATAL_IF(elemCount <= 0); this->reset(elemCount, frameCount); } @@ -221,21 +248,19 @@ void SkiaInterpolator::reset(int elemCount, int frameCount) { fValues = (float*)((char*)fStorage + sizeof(SkTimeCode) * frameCount); } -#define SK_Fixed1Third (SK_Fixed1 / 3) -#define SK_Fixed2Third (SK_Fixed1 * 2 / 3) - static const float gIdentityBlend[4] = {0.33333333f, 0.33333333f, 0.66666667f, 0.66666667f}; bool SkiaInterpolator::setKeyFrame(int index, SkMSec time, const float values[], const float blend[4]) { - SkASSERT(values != nullptr); + LOG_FATAL_IF(values == nullptr); if (blend == nullptr) { blend = gIdentityBlend; } - bool success = ~index == SkTSearch<SkMSec>(&fTimes->fTime, index, time, sizeof(SkTimeCode)); - SkASSERT(success); + // Verify the time should go after all the frames before index + bool success = ~index == SkiaInterpolatorBase::binarySearch(fTimes, index, time); + LOG_FATAL_IF(!success); if (success) { SkTimeCode* timeCode = &fTimes[index]; timeCode->fTime = time; @@ -257,7 +282,7 @@ SkiaInterpolator::Result SkiaInterpolator::timeToValues(SkMSec time, float value if (exact) { memcpy(values, nextSrc, fElemCount * sizeof(float)); } else { - SkASSERT(index > 0); + LOG_FATAL_IF(index <= 0); const float* prevSrc = nextSrc - fElemCount; diff --git a/libs/hwui/SkiaInterpolator.h b/libs/hwui/SkiaInterpolator.h index 9422cb526a8f..62e6c1e33e40 100644 --- a/libs/hwui/SkiaInterpolator.h +++ b/libs/hwui/SkiaInterpolator.h @@ -68,14 +68,16 @@ protected: enum Flags { kMirror = 1, kReset = 2, kHasBlend = 4 }; static float ComputeRelativeT(uint32_t time, uint32_t prevTime, uint32_t nextTime, const float blend[4] = nullptr); - int16_t fFrameCount; - uint8_t fElemCount; - uint8_t fFlags; - float fRepeat; struct SkTimeCode { uint32_t fTime; float fBlend[4]; }; + static int binarySearch(const SkTimeCode* arr, int count, uint32_t target); + + int16_t fFrameCount; + uint8_t fElemCount; + uint8_t fFlags; + float fRepeat; SkTimeCode* fTimes; // pointer into fStorage void* fStorage; }; diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h new file mode 100644 index 000000000000..bd0e35aadbb4 --- /dev/null +++ b/libs/hwui/SkiaWrapper.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#ifndef SKIA_WRAPPER_H_ +#define SKIA_WRAPPER_H_ + +#include <SkRefCnt.h> +#include <utils/RefBase.h> + +namespace android::uirenderer { + +template <typename T> +class SkiaWrapper : public VirtualLightRefBase { +public: + sk_sp<T> getInstance() { + if (mInstance != nullptr && shouldDiscardInstance()) { + mInstance = nullptr; + } + + if (mInstance == nullptr) { + mInstance = createInstance(); + mGenerationId++; + } + return mInstance; + } + + virtual bool shouldDiscardInstance() const { return false; } + + void discardInstance() { mInstance = nullptr; } + + [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; } + +protected: + virtual sk_sp<T> createInstance() = 0; + +private: + sk_sp<T> mInstance = nullptr; + int32_t mGenerationId = 0; +}; + +} // namespace android::uirenderer + +#endif // SKIA_WRAPPER_H_ diff --git a/libs/hwui/SkippedFrameInfo.h b/libs/hwui/SkippedFrameInfo.h new file mode 100644 index 000000000000..de56d9a26982 --- /dev/null +++ b/libs/hwui/SkippedFrameInfo.h @@ -0,0 +1,30 @@ +/* + * Copyright 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 + +namespace android::uirenderer { + +enum class SkippedFrameReason { + DrawingOff, + ContextIsStopped, + NothingToDraw, + NoOutputTarget, + NoBuffer, + AlreadyDrawn, +}; + +} /* namespace android::uirenderer */ diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp index 974a5d05aa84..ae29edf535a2 100644 --- a/libs/hwui/Tonemapper.cpp +++ b/libs/hwui/Tonemapper.cpp @@ -20,6 +20,7 @@ #include <log/log.h> // libshaders only exists on Android devices #ifdef __ANDROID__ +#include <renderthread/CanvasContext.h> #include <shaders/shaders.h> #endif @@ -53,8 +54,17 @@ static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearE ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect)); + auto colorTransform = android::mat4(); + const auto* context = renderthread::CanvasContext::getActiveContext(); + if (context) { + const auto ratio = context->targetSdrHdrRatio(); + if (ratio > 1.0f) { + colorTransform = android::mat4::scale(vec4(ratio, ratio, ratio, 1.f)); + } + } + const auto uniforms = - shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance, + shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance); for (const auto& uniform : uniforms) { diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index 2bff9cb74fa7..ea25f68d7170 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -16,14 +16,16 @@ #pragma once -#include "Properties.h" -#include "utils/Macros.h" - #include <utils/Timers.h> -#include "SkSize.h" +#include <optional> #include <string> +#include "Properties.h" +#include "SkSize.h" +#include "SkippedFrameInfo.h" +#include "utils/Macros.h" + namespace android { namespace uirenderer { @@ -110,13 +112,13 @@ public: // animate itself, such as if hasFunctors is true // This is only set if hasAnimations is true bool requiresUiRedraw = false; - // This is set to true if draw() can be called this frame - // false means that we must delay until the next vsync pulse as frame + // This is set to nullopt if draw() can be called this frame + // A value means that we must delay until the next vsync pulse as frame // production is outrunning consumption - // NOTE that if this is false CanvasContext will set either requiresUiRedraw + // NOTE that if this has a value CanvasContext will set either requiresUiRedraw // *OR* will post itself for the next vsync automatically, use this // only to avoid calling draw() - bool canDrawThisFrame = true; + std::optional<SkippedFrameReason> skippedFrameReason; // Sentinel for animatedImageDelay meaning there is no need to post such // a message. static constexpr nsecs_t kNoAnimatedImageDelay = -1; diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig new file mode 100644 index 000000000000..d074a90d1adf --- /dev/null +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -0,0 +1,8 @@ +package: "com.android.graphics.hwui.flags" + +flag { + name: "limited_hdr" + namespace: "core_graphics" + description: "API to enable apps to restrict the amount of HDR headroom that is used" + bug: "234181960" +}
\ No newline at end of file diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp index c442a7b1d17c..c80a9b4ae97f 100644 --- a/libs/hwui/apex/android_bitmap.cpp +++ b/libs/hwui/apex/android_bitmap.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "Bitmap" #include <log/log.h> #include "android/graphics/bitmap.h" diff --git a/libs/hwui/apex/android_canvas.cpp b/libs/hwui/apex/android_canvas.cpp index 905b123076a2..19f726a31b33 100644 --- a/libs/hwui/apex/android_canvas.cpp +++ b/libs/hwui/apex/android_canvas.cpp @@ -45,9 +45,9 @@ static bool convert(const ANativeWindow_Buffer* buffer, SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs); size_t rowBytes = buffer->stride * imageInfo.bytesPerPixel(); - // If SkSurface::MakeRasterDirect fails then we should as well as we will not be able to + // If SkSurfaces::WrapPixels fails then we should as well as we will not be able to // draw into the canvas. - sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(imageInfo, buffer->bits, rowBytes); + sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(imageInfo, buffer->bits, rowBytes); if (surface.get() != nullptr) { if (outBitmap) { outBitmap->setInfo(imageInfo, rowBytes); diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index 09ae7e78fe23..883f273b5d3d 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -25,9 +25,6 @@ #include <sys/cdefs.h> #include <vulkan/vulkan.h> -#undef LOG_TAG -#define LOG_TAG "AndroidGraphicsJNI" - extern int register_android_graphics_Bitmap(JNIEnv*); extern int register_android_graphics_BitmapFactory(JNIEnv*); extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*); diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp index 613f52b32bea..3ebf7d19202d 100644 --- a/libs/hwui/effects/GainmapRenderer.cpp +++ b/libs/hwui/effects/GainmapRenderer.cpp @@ -54,14 +54,13 @@ float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) { return maxPQLux / GenericSdrWhiteNits; } else if (skcms_TransferFunction_isHLGish(&destTF)) { return maxHLGLux / GenericSdrWhiteNits; - } else { #ifdef __ANDROID__ + } else if (RenderThread::isCurrent()) { CanvasContext* context = CanvasContext::getActiveContext(); return context ? context->targetSdrHdrRatio() : 1.f; -#else - return 1.f; #endif } + return 1.f; } void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src, @@ -245,11 +244,18 @@ private: // This can happen if a BitmapShader is used on multiple canvas', such as a // software + hardware canvas, which is otherwise valid as SkShader is "immutable" std::lock_guard _lock(mUniformGuard); - const float Wunclamped = (sk_float_log(targetHdrSdrRatio) - - sk_float_log(mGainmapInfo.fDisplayRatioSdr)) / - (sk_float_log(mGainmapInfo.fDisplayRatioHdr) - - sk_float_log(mGainmapInfo.fDisplayRatioSdr)); - const float W = std::max(std::min(Wunclamped, 1.f), 0.f); + // Compute the weight parameter that will be used to blend between the images. + float W = 0.f; + if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) { + if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) { + W = (sk_float_log(targetHdrSdrRatio) - + sk_float_log(mGainmapInfo.fDisplayRatioSdr)) / + (sk_float_log(mGainmapInfo.fDisplayRatioHdr) - + sk_float_log(mGainmapInfo.fDisplayRatioSdr)); + } else { + W = 1.f; + } + } mBuilder.uniform("W") = W; uniforms = mBuilder.uniforms(); } diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index 8049dc946c9e..27773a60355a 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -111,7 +111,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() { { std::unique_lock lock{mImageLock}; snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame()); - snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); + snap.mPic = mSkAnimatedImage->makePictureSnapshot(); } return snap; @@ -123,7 +123,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); - snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); + snap.mPic = mSkAnimatedImage->makePictureSnapshot(); snap.mDurationMS = currentFrameDuration(); } diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 92d875bf7f1e..8344a86923ee 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -43,12 +43,15 @@ #include <SkColor.h> #include <SkEncodedImageFormat.h> #include <SkHighContrastFilter.h> -#include <SkImageEncoder.h> +#include <SkImage.h> +#include <SkImageAndroid.h> #include <SkImagePriv.h> #include <SkJpegGainmapEncoder.h> #include <SkPixmap.h> #include <SkRect.h> #include <SkStream.h> +#include <SkJpegEncoder.h> +#include <SkPngEncoder.h> #include <SkWebpEncoder.h> #include <limits> @@ -296,7 +299,8 @@ Bitmap::Bitmap(AHardwareBuffer* buffer, const SkImageInfo& info, size_t rowBytes mPixelStorage.hardware.size = AHardwareBuffer_getAllocationSize(buffer); AHardwareBuffer_acquire(buffer); setImmutable(); // HW bitmaps are always immutable - mImage = SkImage::MakeFromAHardwareBuffer(buffer, mInfo.alphaType(), mInfo.refColorSpace()); + mImage = SkImages::DeferredFromAHardwareBuffer(buffer, mInfo.alphaType(), + mInfo.refColorSpace()); } #endif @@ -407,7 +411,12 @@ sk_sp<SkImage> Bitmap::makeImage() { // Note we don't cache in this case, because the raster image holds a pointer to this Bitmap // internally and ~Bitmap won't be invoked. // TODO: refactor Bitmap to not derive from SkPixelRef, which would allow caching here. +#ifdef __ANDROID__ + // pinnable images are only supported with the Ganesh GPU backend compiled in. + image = SkImages::PinnableRasterFromBitmap(skiaBitmap); +#else image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode); +#endif } return image; } @@ -528,17 +537,25 @@ bool Bitmap::compress(const SkBitmap& bitmap, JavaCompressFormat format, return false; } - SkEncodedImageFormat fm; switch (format) { - case JavaCompressFormat::Jpeg: - fm = SkEncodedImageFormat::kJPEG; - break; + case JavaCompressFormat::Jpeg: { + SkJpegEncoder::Options options; + options.fQuality = quality; + return SkJpegEncoder::Encode(stream, bitmap.pixmap(), options); + } case JavaCompressFormat::Png: - fm = SkEncodedImageFormat::kPNG; - break; - case JavaCompressFormat::Webp: - fm = SkEncodedImageFormat::kWEBP; - break; + return SkPngEncoder::Encode(stream, bitmap.pixmap(), {}); + case JavaCompressFormat::Webp: { + SkWebpEncoder::Options options; + if (quality >= 100) { + options.fCompression = SkWebpEncoder::Compression::kLossless; + options.fQuality = 75; // This is effort to compress + } else { + options.fCompression = SkWebpEncoder::Compression::kLossy; + options.fQuality = quality; + } + return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options); + } case JavaCompressFormat::WebpLossy: case JavaCompressFormat::WebpLossless: { SkWebpEncoder::Options options; @@ -548,8 +565,6 @@ bool Bitmap::compress(const SkBitmap& bitmap, JavaCompressFormat format, return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options); } } - - return SkEncodeImage(stream, bitmap, fm, quality); } sp<uirenderer::Gainmap> Bitmap::gainmap() const { diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index cd8af3d933b1..2351797ac787 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -151,7 +151,7 @@ void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const fl memcpy(outPositions, positions, sizeof(float) * 2 * glyphCount); }; - const minikin::MinikinFont* minikinFont = font.typeface().get(); + const minikin::MinikinFont* minikinFont = font.baseTypeface().get(); SkFont* skfont = &copied.getSkFont(); MinikinFontSkia::populateSkFont(skfont, minikinFont, minikin::FontFakery()); diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index 701a87f0cce4..588463c49497 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -43,9 +43,6 @@ #include <memory> -#undef LOG_TAG -#define LOG_TAG "ImageDecoder" - using namespace android; sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index e359145feef7..bcfb4c89036d 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -84,7 +84,8 @@ void MinikinUtils::getBounds(const Paint* paint, minikin::Bidi bidiFlags, const float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, size_t start, - size_t count, size_t bufSize, float* advances) { + size_t count, size_t bufSize, float* advances, + minikin::MinikinRect* bounds) { minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); const minikin::U16StringPiece textBuf(buf, bufSize); const minikin::Range range(start, start + count); @@ -92,7 +93,7 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit(); return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen, - endHyphen, advances); + endHyphen, advances, bounds); } minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index 009b84b140ea..61bc881faa54 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -51,10 +51,9 @@ public: static void getBounds(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, size_t bufSize, minikin::MinikinRect* out); - static float measureText(const Paint* paint, minikin::Bidi bidiFlags, - const Typeface* typeface, const uint16_t* buf, - size_t start, size_t count, size_t bufSize, - float* advances); + static float measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, + const uint16_t* buf, size_t start, size_t count, size_t bufSize, + float* advances, minikin::MinikinRect* bounds); static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, @@ -76,7 +75,7 @@ public: size_t start = 0; size_t nGlyphs = layout.nGlyphs(); for (size_t i = 0; i < nGlyphs; i++) { - const minikin::MinikinFont* nextFont = layout.getFont(i)->typeface().get(); + const minikin::MinikinFont* nextFont = layout.typeface(i).get(); if (i > 0 && nextFont != curFont) { SkFont* skfont = &paint->getSkFont(); MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start)); diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index 3c67edc9a428..b63ee1bd3d98 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -140,9 +140,8 @@ Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::Font const minikin::FontStyle defaultStyle; const minikin::MinikinFont* mf = - families.empty() - ? nullptr - : families[0]->getClosestMatch(defaultStyle).font->typeface().get(); + families.empty() ? nullptr + : families[0]->getClosestMatch(defaultStyle).typeface().get(); if (mf != nullptr) { SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface(); const SkFontStyle& style = skTypeface->fontStyle(); diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp index a7f5aa83e624..90b1da846205 100644 --- a/libs/hwui/jni/AnimatedImageDrawable.cpp +++ b/libs/hwui/jni/AnimatedImageDrawable.cpp @@ -14,10 +14,6 @@ * limitations under the License. */ -#include "GraphicsJNI.h" -#include "ImageDecoder.h" -#include "Utils.h" - #include <SkAndroidCodec.h> #include <SkAnimatedImage.h> #include <SkColorFilter.h> @@ -27,10 +23,15 @@ #include <SkRect.h> #include <SkRefCnt.h> #include <hwui/AnimatedImageDrawable.h> -#include <hwui/ImageDecoder.h> #include <hwui/Canvas.h> +#include <hwui/ImageDecoder.h> #include <utils/Looper.h> +#include "ColorFilter.h" +#include "GraphicsJNI.h" +#include "ImageDecoder.h" +#include "Utils.h" + using namespace android; static jclass gAnimatedImageDrawableClass; @@ -145,8 +146,9 @@ static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlo static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong nativeFilter) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); - auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter); - drawable->setStagingColorFilter(sk_ref_sp(filter)); + auto filter = uirenderer::ColorFilter::fromJava(nativeFilter); + auto skColorFilter = filter != nullptr ? filter->getInstance() : sk_sp<SkColorFilter>(); + drawable->setStagingColorFilter(skColorFilter); } static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 6ee7576651f2..9e21f860ce21 100644 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -1,5 +1,3 @@ -#undef LOG_TAG -#define LOG_TAG "Bitmap" // #define LOG_NDEBUG 0 #include "Bitmap.h" diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp index 8abcd9a59122..3d0a53440bfb 100644 --- a/libs/hwui/jni/BitmapFactory.cpp +++ b/libs/hwui/jni/BitmapFactory.cpp @@ -1,6 +1,3 @@ -#undef LOG_TAG -#define LOG_TAG "BitmapFactory" - #include "BitmapFactory.h" #include <Gainmap.h> diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp index 740988f77270..ea5c14486ea4 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "BitmapRegionDecoder" - #include "BitmapRegionDecoder.h" #include <HardwareBitmapUploader.h> diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp index 4bd7ef47b871..0b95148d3c82 100644 --- a/libs/hwui/jni/ColorFilter.cpp +++ b/libs/hwui/jni/ColorFilter.cpp @@ -15,20 +15,21 @@ ** limitations under the License. */ -#include "GraphicsJNI.h" +#include "ColorFilter.h" +#include "GraphicsJNI.h" #include "SkBlendMode.h" -#include "SkColorFilter.h" -#include "SkColorMatrixFilter.h" namespace android { using namespace uirenderer; -class SkColorFilterGlue { +class ColorFilterGlue { public: - static void SafeUnref(SkColorFilter* filter) { - SkSafeUnref(filter); + static void SafeUnref(ColorFilter* filter) { + if (filter) { + filter->decStrong(nullptr); + } } static jlong GetNativeFinalizer(JNIEnv*, jobject) { @@ -36,41 +37,75 @@ public: } static jlong CreateBlendModeFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) { - SkBlendMode mode = static_cast<SkBlendMode>(modeHandle); - return reinterpret_cast<jlong>(SkColorFilters::Blend(srcColor, mode).release()); + auto mode = static_cast<SkBlendMode>(modeHandle); + auto* blendModeFilter = new BlendModeColorFilter(srcColor, mode); + blendModeFilter->incStrong(nullptr); + return static_cast<jlong>(reinterpret_cast<uintptr_t>(blendModeFilter)); } static jlong CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) { - return reinterpret_cast<jlong>(SkColorMatrixFilter::MakeLightingFilter(mul, add).release()); + auto* lightingFilter = new LightingFilter(mul, add); + lightingFilter->incStrong(nullptr); + return static_cast<jlong>(reinterpret_cast<uintptr_t>(lightingFilter)); } - static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) { - float matrix[20]; - env->GetFloatArrayRegion(jarray, 0, 20, matrix); + static void SetLightingFilterMul(JNIEnv* env, jobject, jlong lightingFilterPtr, jint mul) { + auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr); + if (filter) { + filter->setMul(mul); + } + } + + static void SetLightingFilterAdd(JNIEnv* env, jobject, jlong lightingFilterPtr, jint add) { + auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr); + if (filter) { + filter->setAdd(add); + } + } + + static std::vector<float> getMatrixFromJFloatArray(JNIEnv* env, jfloatArray jarray) { + std::vector<float> matrix(20); + // float matrix[20]; + env->GetFloatArrayRegion(jarray, 0, 20, matrix.data()); // java biases the translates by 255, so undo that before calling skia matrix[ 4] *= (1.0f/255); matrix[ 9] *= (1.0f/255); matrix[14] *= (1.0f/255); matrix[19] *= (1.0f/255); - return reinterpret_cast<jlong>(SkColorFilters::Matrix(matrix).release()); + return matrix; + } + + static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) { + std::vector<float> matrix = getMatrixFromJFloatArray(env, jarray); + auto* colorMatrixColorFilter = new ColorMatrixColorFilter(std::move(matrix)); + colorMatrixColorFilter->incStrong(nullptr); + return static_cast<jlong>(reinterpret_cast<uintptr_t>(colorMatrixColorFilter)); + } + + static void SetColorMatrix(JNIEnv* env, jobject, jlong colorMatrixColorFilterPtr, + jfloatArray jarray) { + auto* filter = reinterpret_cast<ColorMatrixColorFilter*>(colorMatrixColorFilterPtr); + if (filter) { + filter->setMatrix(getMatrixFromJFloatArray(env, jarray)); + } } }; static const JNINativeMethod colorfilter_methods[] = { - {"nativeGetFinalizer", "()J", (void*) SkColorFilterGlue::GetNativeFinalizer } -}; + {"nativeGetFinalizer", "()J", (void*)ColorFilterGlue::GetNativeFinalizer}}; static const JNINativeMethod blendmode_methods[] = { - { "native_CreateBlendModeFilter", "(II)J", (void*) SkColorFilterGlue::CreateBlendModeFilter }, + {"native_CreateBlendModeFilter", "(II)J", (void*)ColorFilterGlue::CreateBlendModeFilter}, }; static const JNINativeMethod lighting_methods[] = { - { "native_CreateLightingFilter", "(II)J", (void*) SkColorFilterGlue::CreateLightingFilter }, -}; + {"native_CreateLightingFilter", "(II)J", (void*)ColorFilterGlue::CreateLightingFilter}, + {"native_SetLightingFilterAdd", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterAdd}, + {"native_SetLightingFilterMul", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterMul}}; static const JNINativeMethod colormatrix_methods[] = { - { "nativeColorMatrixFilter", "([F)J", (void*) SkColorFilterGlue::CreateColorMatrixFilter }, -}; + {"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter}, + {"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}}; int register_android_graphics_ColorFilter(JNIEnv* env) { android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods, diff --git a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp index 15e529e169fc..a66d3b860ade 100644 --- a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp +++ b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp @@ -1,11 +1,11 @@ #include "CreateJavaOutputStreamAdaptor.h" #include "SkData.h" -#include "SkMalloc.h" #include "SkRefCnt.h" #include "SkStream.h" #include "SkTypes.h" #include "Utils.h" +#include <cstdlib> #include <nativehelper/JNIHelp.h> #include <log/log.h> #include <memory> @@ -177,6 +177,10 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray s return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions); } +static void free_pointer_skproc(const void* ptr, void*) { + free((void*)ptr); +} + sk_sp<SkData> CopyJavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray storage) { std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, inputStream, storage)); if (!stream) { @@ -186,19 +190,31 @@ sk_sp<SkData> CopyJavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray s size_t bufferSize = 4096; size_t streamLen = 0; size_t len; - char* data = (char*)sk_malloc_throw(bufferSize); + char* data = (char*)malloc(bufferSize); + LOG_ALWAYS_FATAL_IF(!data); while ((len = stream->read(data + streamLen, bufferSize - streamLen)) != 0) { streamLen += len; if (streamLen == bufferSize) { bufferSize *= 2; - data = (char*)sk_realloc_throw(data, bufferSize); + data = (char*)realloc(data, bufferSize); + LOG_ALWAYS_FATAL_IF(!data); } } - data = (char*)sk_realloc_throw(data, streamLen); - - return SkData::MakeFromMalloc(data, streamLen); + if (streamLen == 0) { + // realloc with size 0 is unspecified behavior in C++11 + free(data); + data = nullptr; + } else { + // Trim down the buffer to the actual size of the data. + LOG_FATAL_IF(streamLen > bufferSize); + data = (char*)realloc(data, streamLen); + LOG_ALWAYS_FATAL_IF(!data); + } + // Just in case sk_free differs from free, we ask Skia to use + // free to cleanup the buffer that SkData wraps. + return SkData::MakeWithProc(data, streamLen, free_pointer_skproc, nullptr); } /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp index 28e71d74e5b9..0c3af61fc089 100644 --- a/libs/hwui/jni/FontFamily.cpp +++ b/libs/hwui/jni/FontFamily.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "Minikin" - #include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/ScopedUtfChars.h> #include "FontUtils.h" @@ -45,6 +42,7 @@ namespace android { +namespace { struct NativeFamilyBuilder { NativeFamilyBuilder(uint32_t langId, int variant) : langId(langId), variant(static_cast<minikin::FamilyVariant>(variant)) {} @@ -53,6 +51,7 @@ struct NativeFamilyBuilder { std::vector<std::shared_ptr<minikin::Font>> fonts; std::vector<minikin::FontVariation> axes; }; +} // namespace static inline NativeFamilyBuilder* toNativeBuilder(jlong ptr) { return reinterpret_cast<NativeFamilyBuilder*>(ptr); @@ -87,7 +86,8 @@ static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) { } std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create( builder->langId, builder->variant, std::move(builder->fonts), - true /* isCustomFallback */, false /* isDefaultFallback */); + true /* isCustomFallback */, false /* isDefaultFallback */, + minikin::VariationFamilyType::None); if (family->getCoverage().length() == 0) { return 0; } diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp index 78b4f7b7654d..7cc48661619a 100644 --- a/libs/hwui/jni/Graphics.cpp +++ b/libs/hwui/jni/Graphics.cpp @@ -1,6 +1,3 @@ -#undef LOG_TAG -#define LOG_TAG "GraphicsJNI" - #include <assert.h> #include <unistd.h> diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h index 23ab5dd38b1a..b9fff36d372e 100644 --- a/libs/hwui/jni/GraphicsJNI.h +++ b/libs/hwui/jni/GraphicsJNI.h @@ -125,14 +125,6 @@ public: static jobject createBitmapRegionDecoder(JNIEnv* env, android::BitmapRegionDecoderWrapper* bitmap); - /** - * Given a bitmap we natively allocate a memory block to store the contents - * of that bitmap. The memory is then attached to the bitmap via an - * SkPixelRef, which ensures that upon deletion the appropriate caches - * are notified. - */ - static bool allocatePixels(JNIEnv* env, SkBitmap* bitmap); - /** Copy the colors in colors[] to the bitmap, convert to the correct format along the way. Whether to use premultiplied pixels is determined by dstBitmap's alphaType. diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp index e32c9115483c..54369b9e4384 100644 --- a/libs/hwui/jni/GraphicsStatsService.cpp +++ b/libs/hwui/jni/GraphicsStatsService.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "GraphicsStatsService" - #include <JankTracker.h> #include <log/log.h> #include <nativehelper/ScopedPrimitiveArray.h> diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp index 048ce025ce27..cbd452031f69 100644 --- a/libs/hwui/jni/MaskFilter.cpp +++ b/libs/hwui/jni/MaskFilter.cpp @@ -1,6 +1,5 @@ #include "GraphicsJNI.h" #include "SkMaskFilter.h" -#include "SkBlurMask.h" #include "SkBlurMaskFilter.h" #include "SkBlurTypes.h" #include "SkTableMaskFilter.h" @@ -11,6 +10,13 @@ static void ThrowIAE_IfNull(JNIEnv* env, void* ptr) { } } +// From https://skia.googlesource.com/skia/+/d74c99a3cd5eef5f16b2eb226e6b45fe523c8552/src/core/SkBlurMask.cpp#28 +static constexpr float kBLUR_SIGMA_SCALE = 0.57735f; + +static float convertRadiusToSigma(float radius) { + return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f; +} + class SkMaskFilterGlue { public: static void destructor(JNIEnv* env, jobject, jlong filterHandle) { @@ -19,7 +25,7 @@ public: } static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) { - SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius); + SkScalar sigma = convertRadiusToSigma(radius); SkMaskFilter* filter = SkMaskFilter::MakeBlur((SkBlurStyle)blurStyle, sigma).release(); ThrowIAE_IfNull(env, filter); return reinterpret_cast<jlong>(filter); @@ -34,7 +40,7 @@ public: direction[i] = values[i]; } - SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius); + SkScalar sigma = convertRadiusToSigma(radius); SkMaskFilter* filter = SkBlurMaskFilter::MakeEmboss(sigma, direction, ambient, specular).release(); ThrowIAE_IfNull(env, filter); diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp index d50a8a22b5cb..67ef143e6179 100644 --- a/libs/hwui/jni/NinePatch.cpp +++ b/libs/hwui/jni/NinePatch.cpp @@ -15,8 +15,6 @@ ** limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "9patch" #define LOG_NDEBUG 1 #include <androidfw/ResourceTypes.h> diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 13357fa25e8c..7aef7a51b90c 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -15,16 +15,29 @@ ** limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "Paint" - -#include <utils/Log.h> - -#include "GraphicsJNI.h" +#include <hwui/BlurDrawLooper.h> +#include <hwui/MinikinSkia.h> +#include <hwui/MinikinUtils.h> +#include <hwui/Paint.h> +#include <hwui/Typeface.h> +#include <minikin/GraphemeBreak.h> +#include <minikin/LocaleList.h> +#include <minikin/Measurement.h> +#include <minikin/MinikinPaint.h> +#include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/ScopedStringChars.h> #include <nativehelper/ScopedUtfChars.h> -#include <nativehelper/ScopedPrimitiveArray.h> +#include <unicode/utf16.h> +#include <utils/Log.h> +#include <cassert> +#include <cstring> +#include <memory> +#include <vector> + +#include "ColorFilter.h" +#include "GraphicsJNI.h" +#include "SkBlendMode.h" #include "SkColorFilter.h" #include "SkColorSpace.h" #include "SkFont.h" @@ -35,27 +48,21 @@ #include "SkPathEffect.h" #include "SkPathUtils.h" #include "SkShader.h" -#include "SkBlendMode.h" #include "unicode/uloc.h" #include "utils/Blur.h" -#include <hwui/BlurDrawLooper.h> -#include <hwui/MinikinSkia.h> -#include <hwui/MinikinUtils.h> -#include <hwui/Paint.h> -#include <hwui/Typeface.h> -#include <minikin/GraphemeBreak.h> -#include <minikin/LocaleList.h> -#include <minikin/Measurement.h> -#include <minikin/MinikinPaint.h> -#include <unicode/utf16.h> +namespace android { -#include <cassert> -#include <cstring> -#include <memory> -#include <vector> +namespace { -namespace android { +void copyMinikinRectToSkRect(const minikin::MinikinRect& minikinRect, SkRect* skRect) { + skRect->fLeft = minikinRect.mLeft; + skRect->fTop = minikinRect.mTop; + skRect->fRight = minikinRect.mRight; + skRect->fBottom = minikinRect.mBottom; +} + +} // namespace static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count, const SkPoint pos[], SkPath* dst) { @@ -105,8 +112,8 @@ namespace PaintGlue { float measured = 0; std::unique_ptr<float[]> advancesArray(new float[count]); - MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, - 0, count, count, advancesArray.get()); + MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, + count, count, advancesArray.get(), nullptr); for (int i = 0; i < count; i++) { // traverse in the given direction @@ -196,9 +203,9 @@ namespace PaintGlue { if (advances) { advancesArray.reset(new jfloat[count]); } - const float advance = MinikinUtils::measureText(paint, - static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, contextCount, - advancesArray.get()); + const float advance = MinikinUtils::measureText( + paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, + contextCount, advancesArray.get(), nullptr); if (advances) { env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get()); } @@ -236,7 +243,7 @@ namespace PaintGlue { minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; std::unique_ptr<float[]> advancesArray(new float[count]); MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count, - advancesArray.get()); + advancesArray.get(), nullptr); size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text, start, count, offset, moveOpt); return static_cast<jint>(result); @@ -500,7 +507,7 @@ namespace PaintGlue { static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface, const jchar buf[], jint start, jint count, jint bufSize, jboolean isRtl, jint offset, jfloatArray advances, - jint advancesIndex) { + jint advancesIndex, SkRect* drawBounds) { if (advances) { size_t advancesLength = env->GetArrayLength(advances); if ((size_t)(count + advancesIndex) > advancesLength) { @@ -509,14 +516,23 @@ namespace PaintGlue { } } minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + minikin::MinikinRect bounds; if (offset == start + count && advances == nullptr) { - return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, - bufSize, nullptr); + float result = + MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, + bufSize, nullptr, drawBounds ? &bounds : nullptr); + if (drawBounds) { + copyMinikinRectToSkRect(bounds, drawBounds); + } + return result; } std::unique_ptr<float[]> advancesArray(new float[count]); MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, - advancesArray.get()); + advancesArray.get(), drawBounds ? &bounds : nullptr); + if (drawBounds) { + copyMinikinRectToSkRect(bounds, drawBounds); + } float result = minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset); if (advances) { minikin::distributeAdvances(advancesArray.get(), buf, start, count); @@ -532,7 +548,7 @@ namespace PaintGlue { ScopedCharArrayRO textArray(env, text); jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart, start - contextStart, end - start, contextEnd - contextStart, - isRtl, offset - contextStart, nullptr, 0); + isRtl, offset - contextStart, nullptr, 0, nullptr); return result; } @@ -540,13 +556,19 @@ namespace PaintGlue { jcharArray text, jint start, jint end, jint contextStart, jint contextEnd, jboolean isRtl, jint offset, - jfloatArray advances, jint advancesIndex) { + jfloatArray advances, jint advancesIndex, + jobject drawBounds) { const Paint* paint = reinterpret_cast<Paint*>(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); ScopedCharArrayRO textArray(env, text); + SkRect skDrawBounds; jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart, start - contextStart, end - start, contextEnd - contextStart, - isRtl, offset - contextStart, advances, advancesIndex); + isRtl, offset - contextStart, advances, advancesIndex, + drawBounds ? &skDrawBounds : nullptr); + if (drawBounds != nullptr) { + GraphicsJNI::rect_to_jrectf(skDrawBounds, env, drawBounds); + } return result; } @@ -555,7 +577,7 @@ namespace PaintGlue { minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; std::unique_ptr<float[]> advancesArray(new float[count]); MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, - advancesArray.get()); + advancesArray.get(), nullptr); return minikin::getOffsetForAdvance(advancesArray.get(), buf, start, count, advance); } @@ -584,7 +606,7 @@ namespace PaintGlue { minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle); float saveSkewX = font->getSkewX(); bool savefakeBold = font->isEmbolden(); - MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery); + MinikinFontSkia::populateSkFont(font, baseFont.typeface().get(), baseFont.fakery); SkScalar spacing = font->getMetrics(metrics); // The populateSkPaint call may have changed fake bold / text skew // because we want to measure with those effects applied, so now @@ -821,9 +843,11 @@ namespace PaintGlue { static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) { Paint* obj = reinterpret_cast<Paint *>(objHandle); - SkColorFilter* filter = reinterpret_cast<SkColorFilter *>(filterHandle); - obj->setColorFilter(sk_ref_sp(filter)); - return reinterpret_cast<jlong>(obj->getColorFilter()); + auto colorFilter = uirenderer::ColorFilter::fromJava(filterHandle); + auto skColorFilter = + colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>(); + obj->setColorFilter(skColorFilter); + return filterHandle; } static void setXfermode(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint xfermodeHandle) { @@ -1083,7 +1107,7 @@ static const JNINativeMethod methods[] = { (void*)PaintGlue::getCharArrayBounds}, {"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph}, {"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F}, - {"nGetRunCharacterAdvance", "(J[CIIIIZI[FI)F", + {"nGetRunCharacterAdvance", "(J[CIIIIZI[FILandroid/graphics/RectF;)F", (void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F}, {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I}, {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp index f3db1705e694..dcd3fa4932fc 100644 --- a/libs/hwui/jni/RenderEffect.cpp +++ b/libs/hwui/jni/RenderEffect.cpp @@ -14,13 +14,13 @@ * limitations under the License. */ #include "Bitmap.h" +#include "ColorFilter.h" #include "GraphicsJNI.h" #include "SkBlendMode.h" #include "SkImageFilter.h" #include "SkImageFilters.h" #include "graphics_jni_helpers.h" #include "utils/Blur.h" -#include <utils/Log.h> using namespace android::uirenderer; @@ -76,11 +76,13 @@ static jlong createColorFilterEffect( jlong colorFilterHandle, jlong inputFilterHandle ) { - auto* colorFilter = reinterpret_cast<const SkColorFilter*>(colorFilterHandle); + auto colorFilter = android::uirenderer::ColorFilter::fromJava(colorFilterHandle); + auto skColorFilter = + colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>(); auto* inputFilter = reinterpret_cast<const SkImageFilter*>(inputFilterHandle); - sk_sp<SkImageFilter> colorFilterImageFilter = SkImageFilters::ColorFilter( - sk_ref_sp(colorFilter), sk_ref_sp(inputFilter), nullptr); - return reinterpret_cast<jlong>(colorFilterImageFilter.release()); + sk_sp<SkImageFilter> colorFilterImageFilter = + SkImageFilters::ColorFilter(skColorFilter, sk_ref_sp(inputFilter), nullptr); + return reinterpret_cast<jlong>(colorFilterImageFilter.release()); } static jlong createBlendModeEffect( diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index 7eb79be6f55b..2c13ceb77b52 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -1,6 +1,3 @@ -#undef LOG_TAG -#define LOG_TAG "ShaderJNI" - #include <vector> #include "Gainmap.h" diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp index 69418b09fee6..4dbfa88d6301 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.cpp +++ b/libs/hwui/jni/YuvToJpegEncoder.cpp @@ -1,6 +1,3 @@ -#undef LOG_TAG -#define LOG_TAG "YuvToJpegEncoder" - #include "CreateJavaOutputStreamAdaptor.h" #include "SkStream.h" #include "YuvToJpegEncoder.h" diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp index f060bb32031a..426644ee6a4e 100644 --- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp +++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp @@ -84,7 +84,7 @@ static void android_view_DisplayListCanvas_resetDisplayListCanvas(CRITICAL_JNI_P canvas->resetRecording(width, height, renderNode); } -static jint android_view_DisplayListCanvas_getMaxTextureSize(CRITICAL_JNI_PARAMS) { +static jint android_view_DisplayListCanvas_getMaxTextureSize(JNIEnv*, jobject) { #ifdef __ANDROID__ // Layoutlib does not support RenderProxy (RenderThread) return android::uirenderer::renderthread::RenderProxy::maxTextureSize(); #else @@ -175,14 +175,14 @@ static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAM const char* const kClassPathName = "android/graphics/RecordingCanvas"; static JNINativeMethod gMethods[] = { + {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize}, + {"nGetMaximumTextureHeight", "()I", + (void*)android_view_DisplayListCanvas_getMaxTextureSize}, // ------------ @CriticalNative -------------- {"nCreateDisplayListCanvas", "(JII)J", (void*)android_view_DisplayListCanvas_createDisplayListCanvas}, {"nResetDisplayListCanvas", "(JJII)V", (void*)android_view_DisplayListCanvas_resetDisplayListCanvas}, - {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize}, - {"nGetMaximumTextureHeight", "()I", - (void*)android_view_DisplayListCanvas_getMaxTextureSize}, {"nEnableZ", "(JZ)V", (void*)android_view_DisplayListCanvas_enableZ}, {"nFinishRecording", "(JJ)V", (void*)android_view_DisplayListCanvas_finishRecording}, {"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode}, diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp index 706f18c3be80..e3cdee6e7034 100644 --- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "HardwareBufferRenderer" #define ATRACE_TAG ATRACE_TAG_VIEW #include <GraphicsJNI.h> diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index d04de37f6961..422ffeaecfd0 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "ThreadedRenderer" #define ATRACE_TAG ATRACE_TAG_VIEW #include <FrameInfo.h> @@ -27,7 +25,7 @@ #include <SkColorSpace.h> #include <SkData.h> #include <SkImage.h> -#include <SkImagePriv.h> +#include <SkImageAndroid.h> #include <SkPicture.h> #include <SkPixmap.h> #include <SkSerialProcs.h> @@ -35,6 +33,7 @@ #include <SkTypeface.h> #include <dlfcn.h> #include <gui/TraceUtils.h> +#include <include/encode/SkPngEncoder.h> #include <inttypes.h> #include <media/NdkImage.h> #include <media/NdkImageReader.h> @@ -54,6 +53,7 @@ #include <algorithm> #include <atomic> +#include <log/log.h> #include <vector> #include "JvmErrorReporter.h" @@ -477,7 +477,7 @@ public: // actually cross thread boundaries here, make a copy so it's immutable proper if (bitmap && !bitmap->isImmutable()) { ATRACE_NAME("Copying mutable bitmap"); - return SkImage::MakeFromBitmap(*bitmap); + return SkImages::RasterFromBitmap(*bitmap); } if (img->isTextureBacked()) { ATRACE_NAME("Readback of texture image"); @@ -497,7 +497,7 @@ public: return sk_ref_sp(img); } bm.setImmutable(); - return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode); + return SkImages::PinnableRasterFromBitmap(bm); } return sk_ref_sp(img); } @@ -524,7 +524,16 @@ public: if (iter != context->mTextureMap.end()) { img = iter->second.get(); } - return img->encodeToData(); + if (!img) { + return nullptr; + } + // The following encode (specifically the pixel readback) will fail on a + // texture-backed image. They should already be raster images, but on + // the off-chance they aren't, we will just serialize it as nothing. + if (img->isTextureBacked()) { + return SkData::MakeEmpty(); + } + return SkPngEncoder::Encode(nullptr, img, {}); } void serialize(SkWStream* stream) const override { diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index 8c7b9a4b5e94..2a218a25913d 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -243,6 +243,13 @@ static jboolean android_view_RenderNode_setRenderEffect(CRITICAL_JNI_PARAMS_COMM return SET_AND_DIRTY(mutateLayerProperties().setImageFilter, imageFilter, RenderNode::GENERIC); } +static jboolean android_view_RenderNode_setBackdropRenderEffect( + CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jlong renderEffectPtr) { + SkImageFilter* imageFilter = reinterpret_cast<SkImageFilter*>(renderEffectPtr); + return SET_AND_DIRTY(mutateLayerProperties().setBackdropImageFilter, imageFilter, + RenderNode::GENERIC); +} + static jboolean android_view_RenderNode_setHasOverlappingRendering(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, bool hasOverlappingRendering) { return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering, @@ -792,6 +799,8 @@ static const JNINativeMethod gMethods[] = { {"nSetAlpha", "(JF)Z", (void*)android_view_RenderNode_setAlpha}, {"nSetRenderEffect", "(JJ)Z", (void*)android_view_RenderNode_setRenderEffect}, + {"nSetBackdropRenderEffect", "(JJ)Z", + (void*)android_view_RenderNode_setBackdropRenderEffect}, {"nSetHasOverlappingRendering", "(JZ)Z", (void*)android_view_RenderNode_setHasOverlappingRendering}, {"nSetUsageHint", "(JI)V", (void*)android_view_RenderNode_setUsageHint}, diff --git a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp index 764eff9a04be..b86c74fe0e47 100644 --- a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp +++ b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include <Interpolator.h> #include <cutils/log.h> diff --git a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp index c6d26f853c1d..40be9243affb 100644 --- a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp +++ b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include <Animator.h> #include <Interpolator.h> #include <RenderProperties.h> diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp index 9cffceb308c8..ade48f26937f 100644 --- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp +++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp @@ -14,13 +14,13 @@ * limitations under the License. */ -#include "GraphicsJNI.h" +#include <hwui/Paint.h> +#include "ColorFilter.h" +#include "GraphicsJNI.h" #include "PathParser.h" #include "VectorDrawable.h" -#include <hwui/Paint.h> - namespace android { using namespace uirenderer; using namespace uirenderer::VectorDrawable; @@ -108,8 +108,9 @@ static jint draw(JNIEnv* env, jobject, jlong treePtr, jlong canvasPtr, Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); SkRect rect; GraphicsJNI::jrect_to_rect(env, jrect, &rect); - SkColorFilter* colorFilter = reinterpret_cast<SkColorFilter*>(colorFilterPtr); - return tree->draw(canvas, colorFilter, rect, needsMirroring, canReuseCache); + auto colorFilter = ColorFilter::fromJava(colorFilterPtr); + auto skColorFilter = colorFilter != nullptr ? colorFilter->getInstance() : nullptr; + return tree->draw(canvas, skColorFilter.get(), rect, needsMirroring, canReuseCache); } /** diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index 1af60b2f5fae..2ec94c954fe9 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "Minikin" - #include "Font.h" #include "SkData.h" #include "SkFont.h" @@ -127,7 +124,7 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong builderPtr, jint weight, jboolean italic, jint ttcIndex) { FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr); - MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get()); + MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get()); std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr)); // Reconstruct SkTypeface with different arguments from existing SkTypeface. @@ -159,7 +156,7 @@ static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong static jfloat Font_getGlyphBounds(JNIEnv* env, jobject, jlong fontHandle, jint glyphId, jlong paintHandle, jobject rect) { FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle); - MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get()); + MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get()); Paint* paint = reinterpret_cast<Paint*>(paintHandle); SkFont* skFont = &paint->getSkFont(); @@ -179,7 +176,7 @@ static jfloat Font_getGlyphBounds(JNIEnv* env, jobject, jlong fontHandle, jint g static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong paintHandle, jobject metricsObj) { FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle); - MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get()); + MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get()); Paint* paint = reinterpret_cast<Paint*>(paintHandle); SkFont* skFont = &paint->getSkFont(); @@ -209,7 +206,7 @@ static jlong Font_cloneFont(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { // Fast Native static jobject Font_newByteBuffer(JNIEnv* env, jobject, jlong fontPtr) { FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr); - const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface(); + const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface(); return env->NewDirectByteBuffer(const_cast<void*>(minikinFont->GetFontData()), minikinFont->GetFontSize()); } @@ -217,7 +214,7 @@ static jobject Font_newByteBuffer(JNIEnv* env, jobject, jlong fontPtr) { // Critical Native static jlong Font_getBufferAddress(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr); - return reinterpret_cast<jlong>(font->font->typeface()->GetFontData()); + return reinterpret_cast<jlong>(font->font->baseTypeface()->GetFontData()); } // Critical Native @@ -236,7 +233,7 @@ static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontPtr) { } return env->NewStringUTF(path.c_str()); } else { - const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface(); + const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface(); const std::string& path = minikinFont->GetFontPath(); if (path.empty()) { return nullptr; @@ -275,7 +272,7 @@ static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { reader.skipString(); // fontPath return reader.read<int>(); } else { - const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface(); + const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface(); return minikinFont->GetFontIndex(); } } @@ -289,7 +286,7 @@ static jint Font_getAxisCount(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { reader.skip<int>(); // fontIndex return reader.readArray<minikin::FontVariation>().second; } else { - const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface(); + const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface(); return minikinFont->GetAxes().size(); } } @@ -304,7 +301,7 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde reader.skip<int>(); // fontIndex var = reader.readArray<minikin::FontVariation>().first[index]; } else { - const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface(); + const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface(); var = minikinFont->GetAxes().at(index); } uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value); @@ -314,7 +311,7 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde // Critical Native static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr); - return font->font->typeface()->GetSourceId(); + return font->font->baseTypeface()->GetSourceId(); } static jlongArray Font_getAvailableFontSet(JNIEnv* env, jobject) { diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp index 897c4d71c0d5..462c8c8f2fb0 100644 --- a/libs/hwui/jni/fonts/FontFamily.cpp +++ b/libs/hwui/jni/fonts/FontFamily.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "Minikin" - #include "graphics_jni_helpers.h" #include <nativehelper/ScopedUtfChars.h> @@ -29,9 +26,11 @@ namespace android { +namespace { struct NativeFamilyBuilder { std::vector<std::shared_ptr<minikin::Font>> fonts; }; +} // namespace static inline NativeFamilyBuilder* toBuilder(jlong ptr) { return reinterpret_cast<NativeFamilyBuilder*>(ptr); @@ -58,7 +57,7 @@ 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, - jboolean isDefaultFallback) { + jboolean isDefaultFallback, jint variationFamilyType) { std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr)); uint32_t localeId; if (langTags == nullptr) { @@ -69,7 +68,8 @@ static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderP } std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create( localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts), - isCustomFallback, isDefaultFallback); + isCustomFallback, isDefaultFallback, + static_cast<minikin::VariationFamilyType>(variationFamilyType)); if (family->getCoverage().length() == 0) { // No coverage means minikin rejected given font for some reasons. jniThrowException(env, "java/lang/IllegalArgumentException", @@ -119,7 +119,7 @@ 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;IZZ)J", (void*)FontFamily_Builder_build}, + {"nBuild", "(JLjava/lang/String;IZZI)J", (void*)FontFamily_Builder_build}, {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc}, }; diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp index 427bafa1bd83..3b18f5f54187 100644 --- a/libs/hwui/jni/pdf/PdfEditor.cpp +++ b/libs/hwui/jni/pdf/PdfEditor.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "PdfEditor" - #include <sys/types.h> #include <unistd.h> diff --git a/libs/hwui/jni/pdf/PdfUtils.cpp b/libs/hwui/jni/pdf/PdfUtils.cpp index 06d202828b85..6887fdacd68f 100644 --- a/libs/hwui/jni/pdf/PdfUtils.cpp +++ b/libs/hwui/jni/pdf/PdfUtils.cpp @@ -16,14 +16,11 @@ #include "PdfUtils.h" -#include "jni.h" #include <nativehelper/JNIHelp.h> +#include <utils/Log.h> #include "fpdfview.h" - -#undef LOG_TAG -#define LOG_TAG "PdfUtils" -#include <utils/Log.h> +#include "jni.h" namespace android { diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp index 55f03bd9f7b1..322af7e9f3ee 100644 --- a/libs/hwui/jni/text/GraphemeBreak.cpp +++ b/libs/hwui/jni/text/GraphemeBreak.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "GraphemeBreaker" - #include <minikin/GraphemeBreak.h> #include <nativehelper/ScopedPrimitiveArray.h> diff --git a/libs/hwui/jni/text/LineBreaker.cpp b/libs/hwui/jni/text/LineBreaker.cpp index 69865171a09d..c512256ed9b9 100644 --- a/libs/hwui/jni/text/LineBreaker.cpp +++ b/libs/hwui/jni/text/LineBreaker.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "LineBreaker" - #include "utils/misc.h" #include "utils/Log.h" #include "graphics_jni_helpers.h" @@ -54,13 +51,12 @@ static inline minikin::android::StaticLayoutNative* toNative(jlong ptr) { // set text and set a number of parameters for creating a layout (width, tabstops, strategy, // hyphenFrequency) -static jlong nInit(JNIEnv* env, jclass /* unused */, - jint breakStrategy, jint hyphenationFrequency, jboolean isJustified, jintArray indents) { +static jlong nInit(JNIEnv* env, jclass /* unused */, jint breakStrategy, jint hyphenationFrequency, + jboolean isJustified, jintArray indents, jboolean useBoundsForWidth) { return reinterpret_cast<jlong>(new minikin::android::StaticLayoutNative( static_cast<minikin::BreakStrategy>(breakStrategy), - static_cast<minikin::HyphenationFrequency>(hyphenationFrequency), - isJustified, - jintArrayToFloatVector(env, indents))); + static_cast<minikin::HyphenationFrequency>(hyphenationFrequency), isJustified, + jintArrayToFloatVector(env, indents), useBoundsForWidth)); } static void nFinish(jlong nativePtr) { @@ -131,39 +127,44 @@ static jlong nGetReleaseResultFunc(CRITICAL_JNI_PARAMS) { } static const JNINativeMethod gMethods[] = { - // Fast Natives - {"nInit", "(" - "I" // breakStrategy - "I" // hyphenationFrequency - "Z" // isJustified - "[I" // indents - ")J", (void*) nInit}, - - // Critical Natives - {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, - - // Regular JNI - {"nComputeLineBreaks", "(" - "J" // nativePtr - "[C" // text - "J" // MeasuredParagraph ptr. - "I" // length - "F" // firstWidth - "I" // firstWidthLineCount - "F" // restWidth - "[F" // variableTabStops - "F" // defaultTabStop - "I" // indentsOffset - ")J", (void*) nComputeLineBreaks}, - - // Result accessors, CriticalNatives - {"nGetLineCount", "(J)I", (void*)nGetLineCount}, - {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset}, - {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth}, - {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent}, - {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent}, - {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag}, - {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc}, + // Fast Natives + {"nInit", + "(" + "I" // breakStrategy + "I" // hyphenationFrequency + "Z" // isJustified + "[I" // indents + "Z" // useBoundsForWidth + ")J", + (void*)nInit}, + + // Critical Natives + {"nGetReleaseFunc", "()J", (void*)nGetReleaseFunc}, + + // Regular JNI + {"nComputeLineBreaks", + "(" + "J" // nativePtr + "[C" // text + "J" // MeasuredParagraph ptr. + "I" // length + "F" // firstWidth + "I" // firstWidthLineCount + "F" // restWidth + "[F" // variableTabStops + "F" // defaultTabStop + "I" // indentsOffset + ")J", + (void*)nComputeLineBreaks}, + + // Result accessors, CriticalNatives + {"nGetLineCount", "(J)I", (void*)nGetLineCount}, + {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset}, + {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth}, + {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent}, + {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent}, + {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag}, + {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc}, }; int register_android_graphics_text_LineBreaker(JNIEnv* env) { diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp index c13c800651ef..746745afbf09 100644 --- a/libs/hwui/jni/text/MeasuredText.cpp +++ b/libs/hwui/jni/text/MeasuredText.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "MeasuredText" - #include "GraphicsJNI.h" #include "utils/misc.h" #include "utils/Log.h" @@ -65,13 +62,14 @@ static jlong nInitBuilder(CRITICAL_JNI_PARAMS) { // Regular JNI static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, - jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end, - jboolean isRtl) { + jlong paintPtr, jint lbStyle, jint lbWordStyle, jboolean hyphenation, + jint start, jint end, jboolean isRtl) { Paint* paint = toPaint(paintPtr); const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface()); minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface); toBuilder(builderPtr) - ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl); + ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, hyphenation, + isRtl); } // Regular JNI @@ -84,13 +82,14 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong // Regular JNI static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, jlong hintPtr, jcharArray javaText, jboolean computeHyphenation, - jboolean computeLayout, jboolean fastHyphenationMode) { + jboolean computeLayout, jboolean computeBounds, + jboolean fastHyphenationMode) { ScopedCharArrayRO text(env, javaText); const minikin::U16StringPiece textBuffer(text.get(), text.size()); // Pass the ownership to Java. return toJLong(toBuilder(builderPtr) - ->build(textBuffer, computeHyphenation, computeLayout, + ->build(textBuffer, computeHyphenation, computeLayout, computeBounds, fastHyphenationMode, toMeasuredParagraph(hintPtr)) .release()); } @@ -161,9 +160,9 @@ static jint nGetMemoryUsage(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { static const JNINativeMethod gMTBuilderMethods[] = { // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*)nInitBuilder}, - {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun}, + {"nAddStyleRun", "(JJIIZIIZ)V", (void*)nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun}, - {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText}, + {"nBuildMeasuredText", "(JJ[CZZZZ)J", (void*)nBuildMeasuredText}, {"nFreeBuilder", "(J)V", (void*)nFreeBuilder}, }; diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp index 8e4dd53069f4..6c05346d26da 100644 --- a/libs/hwui/jni/text/TextShaper.cpp +++ b/libs/hwui/jni/text/TextShaper.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#undef LOG_TAG -#define LOG_TAG "TextShaper" - #include "graphics_jni_helpers.h" #include <nativehelper/ScopedStringChars.h> #include <nativehelper/ScopedPrimitiveArray.h> @@ -62,7 +59,7 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou const minikin::Font* font = layout.getFont(i); if (seenFonts.find(font) != seenFonts.end()) continue; minikin::MinikinExtent extent = {}; - font->typeface()->GetFontExtent(&extent, minikinPaint, layout.getFakery(i)); + layout.typeface(i)->GetFontExtent(&extent, minikinPaint, layout.getFakery(i)); overallAscent = std::min(overallAscent, extent.ascent); overallDescent = std::max(overallDescent, extent.descent); } @@ -148,6 +145,30 @@ static jfloat TextShaper_Result_getY(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i } // CriticalNative +static jboolean TextShaper_Result_getFakeBold(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).isFakeBold(); +} + +// CriticalNative +static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).isFakeItalic(); +} + +// CriticalNative +static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).wghtAdjustment(); +} + +// CriticalNative +static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).italAdjustment(); +} + +// CriticalNative static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); std::shared_ptr<minikin::Font> fontRef = layout->layout.getFontRef(i); @@ -185,15 +206,19 @@ static const JNINativeMethod gMethods[] = { }; static const JNINativeMethod gResultMethods[] = { - { "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount }, - { "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance }, - { "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent }, - { "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent }, - { "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId }, - { "nGetX", "(JI)F", (void*)TextShaper_Result_getX }, - { "nGetY", "(JI)F", (void*)TextShaper_Result_getY }, - { "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont }, - { "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc }, + {"nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount}, + {"nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance}, + {"nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent}, + {"nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent}, + {"nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId}, + {"nGetX", "(JI)F", (void*)TextShaper_Result_getX}, + {"nGetY", "(JI)F", (void*)TextShaper_Result_getY}, + {"nGetFont", "(JI)J", (void*)TextShaper_Result_getFont}, + {"nGetFakeBold", "(JI)Z", (void*)TextShaper_Result_getFakeBold}, + {"nGetFakeItalic", "(JI)Z", (void*)TextShaper_Result_getFakeItalic}, + {"nGetWeightOverride", "(JI)F", (void*)TextShaper_Result_getWeightOverride}, + {"nGetItalicOverride", "(JI)F", (void*)TextShaper_Result_getItalicOverride}, + {"nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc}, }; int register_android_graphics_text_TextShaper(JNIEnv* env) { diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp new file mode 100644 index 000000000000..e81cbfb508ae --- /dev/null +++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp @@ -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. + */ + +#include "BackdropFilterDrawable.h" + +#include <SkImage.h> +#include <SkSurface.h> + +#include "RenderNode.h" +#include "RenderNodeDrawable.h" +#ifdef __ANDROID__ +#include "include/gpu/ganesh/SkImageGanesh.h" +#endif + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +BackdropFilterDrawable::~BackdropFilterDrawable() {} + +bool BackdropFilterDrawable::prepareToDraw(SkCanvas* canvas, const RenderProperties& properties, + int backdropImageWidth, int backdropImageHeight) { + // the drawing bounds for blurred content. + mDstBounds.setWH(properties.getWidth(), properties.getHeight()); + + float alphaMultiplier = 1.0f; + RenderNodeDrawable::setViewProperties(properties, canvas, &alphaMultiplier, true); + + // get proper subset for previous content. + canvas->getTotalMatrix().mapRect(&mImageSubset, mDstBounds); + SkRect imageSubset(mImageSubset); + // ensure the subset is inside bounds of previous content. + if (!mImageSubset.intersect(SkRect::MakeWH(backdropImageWidth, backdropImageHeight))) { + return false; + } + + // correct the drawing bounds if subset was changed. + if (mImageSubset != imageSubset) { + SkMatrix inverse; + if (canvas->getTotalMatrix().invert(&inverse)) { + inverse.mapRect(&mDstBounds, mImageSubset); + } + } + + // follow the alpha from the target RenderNode. + mPaint.setAlpha(properties.layerProperties().alpha() * alphaMultiplier); + return true; +} + +void BackdropFilterDrawable::onDraw(SkCanvas* canvas) { + const RenderProperties& properties = mTargetRenderNode->properties(); + auto* backdropFilter = properties.layerProperties().getBackdropImageFilter(); + auto* surface = canvas->getSurface(); + if (!backdropFilter || !surface) { + return; + } + + auto backdropImage = surface->makeImageSnapshot(); + // sync necessary properties from target RenderNode. + if (!prepareToDraw(canvas, properties, backdropImage->width(), backdropImage->height())) { + return; + } + + auto imageSubset = mImageSubset.roundOut(); +#ifdef __ANDROID__ + if (canvas->recordingContext()) { + backdropImage = + SkImages::MakeWithFilter(canvas->recordingContext(), backdropImage, backdropFilter, + imageSubset, imageSubset, &mOutSubset, &mOutOffset); + } else +#endif + { + backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageSubset, + imageSubset, &mOutSubset, &mOutOffset); + } + canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds, + SkSamplingOptions(SkFilterMode::kLinear), &mPaint, + SkCanvas::kStrict_SrcRectConstraint); +} + +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.h b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h new file mode 100644 index 000000000000..9e35837675ae --- /dev/null +++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.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 <SkCanvas.h> +#include <SkDrawable.h> +#include <SkPaint.h> + +namespace android { +namespace uirenderer { + +class RenderNode; +class RenderProperties; + +namespace skiapipeline { + +/** + * This drawable captures it's backdrop content and render it with a + * image filter. + */ +class BackdropFilterDrawable : public SkDrawable { +public: + BackdropFilterDrawable(RenderNode* renderNode, SkCanvas* canvas) + : mTargetRenderNode(renderNode), mBounds(canvas->getLocalClipBounds()) {} + + ~BackdropFilterDrawable(); + +private: + RenderNode* mTargetRenderNode; + SkPaint mPaint; + + SkRect mDstBounds; + SkRect mImageSubset; + SkIRect mOutSubset; + SkIPoint mOutOffset; + + /** + * Check all necessary properties before actual drawing. + * Return true if ready to draw. + */ + bool prepareToDraw(SkCanvas* canvas, const RenderProperties& properties, int backdropImageWidth, + int backdropImageHeight); + +protected: + void onDraw(SkCanvas* canvas) override; + + virtual SkRect onGetBounds() override { return mBounds; } + const SkRect mBounds; +}; + +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index 8d5967bbd461..5d3fb30769ed 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -21,10 +21,15 @@ #include "GrBackendSurface.h" #include "RenderNode.h" #include "SkAndroidFrameworkUtils.h" +#include "SkCanvas.h" +#include "SkCanvasAndroid.h" #include "SkClipStack.h" #include "SkRect.h" #include "SkM44.h" +#include <include/gpu/ganesh/SkSurfaceGanesh.h> #include "include/gpu/GpuTypes.h" // from Skia +#include <include/gpu/gl/GrGLTypes.h> +#include <include/gpu/ganesh/gl/GrGLBackendSurface.h> #include "utils/GLUtils.h" #include <effects/GainmapRenderer.h> #include "renderthread/CanvasContext.h" @@ -34,7 +39,7 @@ namespace uirenderer { namespace skiapipeline { static void setScissor(int viewportHeight, const SkIRect& clip) { - SkASSERT(!clip.isEmpty()); + LOG_FATAL_IF(clip.isEmpty(), "empty scissor clip"); // transform to Y-flipped GL space, and prevent negatives GLint y = viewportHeight - clip.fBottom; GLint height = (viewportHeight - clip.fTop) - y; @@ -42,9 +47,9 @@ static void setScissor(int viewportHeight, const SkIRect& clip) { } static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) { - GrBackendRenderTarget renderTarget = canvas->topLayerBackendRenderTarget(); + GrBackendRenderTarget renderTarget = skgpu::ganesh::TopLayerBackendRenderTarget(canvas); GrGLFramebufferInfo fboInfo; - LOG_ALWAYS_FATAL_IF(!renderTarget.getGLFramebufferInfo(&fboInfo), + LOG_ALWAYS_FATAL_IF(!GrBackendRenderTargets::GetGLFramebufferInfo(renderTarget, &fboInfo), "getGLFrameBufferInfo failed"); *outFboID = fboInfo.fFBOID; @@ -76,13 +81,13 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { } // flush will create a GrRenderTarget if not already present. - canvas->flush(); + directContext->flushAndSubmit(); GLuint fboID = 0; SkISize fboSize; GetFboDetails(canvas, &fboID, &fboSize); - SkIRect surfaceBounds = canvas->topLayerBounds(); + SkIRect surfaceBounds = skgpu::ganesh::TopLayerBounds(canvas); SkIRect clipBounds = canvas->getDeviceClipBounds(); SkM44 mat4(canvas->getLocalToDevice()); SkRegion clipRegion; @@ -95,12 +100,14 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { SkImageInfo surfaceInfo = canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height()); tmpSurface = - SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo); + SkSurfaces::RenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo); tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT); GrGLFramebufferInfo fboInfo; - if (!tmpSurface->getBackendRenderTarget(SkSurface::kFlushWrite_BackendHandleAccess) - .getGLFramebufferInfo(&fboInfo)) { + if (!GrBackendRenderTargets::GetGLFramebufferInfo( + SkSurfaces::GetBackendRenderTarget( + tmpSurface.get(), SkSurfaces::BackendHandleAccess::kFlushWrite), + &fboInfo)) { ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor"); return; } @@ -163,7 +170,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { // GL ops get inserted here if previous flush is missing, which could dirty the stencil bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas); - tmpCanvas->flush(); // need this flush for the single op that draws into the stencil + directContext->flushAndSubmit(); // need this flush for the single op that draws into the stencil // ensure that the framebuffer that the webview will render into is bound before after we // draw into the stencil diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index da4f66d45a70..2b2e3995d17e 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -15,21 +15,25 @@ */ #include "RenderNodeDrawable.h" + #include <SkPaint.h> #include <SkPaintFilterCanvas.h> #include <SkPoint.h> #include <SkRRect.h> #include <SkRect.h> #include <gui/TraceUtils.h> +#include <include/effects/SkImageFilters.h> +#ifdef __ANDROID__ +#include <include/gpu/ganesh/SkImageGanesh.h> +#endif + +#include <optional> + #include "RenderNode.h" #include "SkiaDisplayList.h" #include "StretchMask.h" #include "TransformCanvas.h" -#include <include/effects/SkImageFilters.h> - -#include <optional> - namespace android { namespace uirenderer { namespace skiapipeline { @@ -255,9 +259,19 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot(); if (imageFilter) { auto subset = SkIRect::MakeWH(srcBounds.width(), srcBounds.height()); - snapshotImage = snapshotImage->makeWithFilter(recordingContext, imageFilter, - subset, clipBounds.roundOut(), - &srcBounds, &offset); + +#ifdef __ANDROID__ + if (recordingContext) { + snapshotImage = SkImages::MakeWithFilter( + recordingContext, snapshotImage, imageFilter, subset, + clipBounds.roundOut(), &srcBounds, &offset); + } else +#endif + { + snapshotImage = SkImages::MakeWithFilter(snapshotImage, imageFilter, subset, + clipBounds.roundOut(), &srcBounds, + &offset); + } } } else { const auto snapshotResult = renderNode->updateSnapshotIfRequired( @@ -362,7 +376,7 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { } void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, SkCanvas* canvas, - float* alphaMultiplier) { + float* alphaMultiplier, bool ignoreLayer) { if (properties.getLeft() != 0 || properties.getTop() != 0) { canvas->translate(properties.getLeft(), properties.getTop()); } @@ -378,7 +392,8 @@ void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, S canvas->concat(*properties.getTransformMatrix()); } } - if (Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale) { + if (Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale && + !ignoreLayer) { const StretchEffect& stretch = properties.layerProperties().getStretchEffect(); if (!stretch.isEmpty()) { canvas->concat( @@ -388,10 +403,10 @@ void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, S const bool isLayer = properties.effectiveLayerType() != LayerType::None; int clipFlags = properties.getClippingFlags(); if (properties.getAlpha() < 1) { - if (isLayer) { + if (isLayer && !ignoreLayer) { clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer } - if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) { + if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering()) || ignoreLayer) { *alphaMultiplier = properties.getAlpha(); } else { // savelayer needed to create an offscreen buffer diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h index c7582e734009..818ac45bf346 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h @@ -120,7 +120,7 @@ private: * Applies the rendering properties of a view onto a SkCanvas. */ static void setViewProperties(const RenderProperties& properties, SkCanvas* canvas, - float* alphaMultiplier); + float* alphaMultiplier, bool ignoreLayer = false); /** * Stores transform on the canvas at time of recording and is used for @@ -149,6 +149,11 @@ private: * display list that is searched for any render nodes with getProjectBackwards==true */ SkiaDisplayList* mProjectedDisplayList = nullptr; + + /** + * Allow BackdropFilterDrawable to apply same render properties onto SkCanvas. + */ + friend class BackdropFilterDrawable; }; } // namespace skiapipeline diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp index 11977bd54c2c..136740c799dd 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp @@ -27,7 +27,6 @@ #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 00919dc3f22a..b87002371775 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -79,7 +79,7 @@ bool ShaderCache::validateCache(const void* identity, ssize_t size) { void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) { ATRACE_NAME("initShaderDiskCache"); - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); // Emulators can switch between different renders either as part of config // or snapshot migration. Also, program binaries may not work well on some @@ -88,23 +88,21 @@ void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) { mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename)); validateCache(identity, size); mInitialized = true; + if (identity != nullptr && size > 0 && mIDHash.size()) { + set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size()); + } } } void ShaderCache::setFilename(const char* filename) { - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); mFilename = filename; } -BlobCache* ShaderCache::getBlobCacheLocked() { - LOG_ALWAYS_FATAL_IF(!mInitialized, "ShaderCache has not been initialized"); - return mBlobCache.get(); -} - sk_sp<SkData> ShaderCache::load(const SkData& key) { ATRACE_NAME("ShaderCache::load"); size_t keySize = key.size(); - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); if (!mInitialized) { return nullptr; } @@ -115,8 +113,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { if (!valueBuffer) { return nullptr; } - BlobCache* bc = getBlobCacheLocked(); - size_t valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize); + size_t valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize); int maxTries = 3; while (valueSize > mObservedBlobValueSize && maxTries > 0) { mObservedBlobValueSize = std::min(valueSize, maxValueSize); @@ -126,7 +123,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { return nullptr; } valueBuffer = newValueBuffer; - valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize); + valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize); maxTries--; } if (!valueSize) { @@ -143,16 +140,17 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { return SkData::MakeFromMalloc(valueBuffer, valueSize); } -namespace { -// Helper for BlobCache::set to trace the result. -void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) { - switch (cache->set(key, keySize, value, valueSize)) { +void ShaderCache::set(const void* key, size_t keySize, const void* value, size_t valueSize) { + switch (mBlobCache->set(key, keySize, value, valueSize)) { case BlobCache::InsertResult::kInserted: // This is what we expect/hope. It means the cache is large enough. return; case BlobCache::InsertResult::kDidClean: { ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize, valueSize); + if (mIDHash.size()) { + set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size()); + } return; } case BlobCache::InsertResult::kNotEnoughSpace: { @@ -172,22 +170,22 @@ void set(BlobCache* cache, const void* key, size_t keySize, const void* value, s } } } -} // namespace void ShaderCache::saveToDiskLocked() { ATRACE_NAME("ShaderCache::saveToDiskLocked"); if (mInitialized && mBlobCache) { - if (mIDHash.size()) { - auto key = sIDKey; - set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size()); - } + // The most straightforward way to make ownership shared + mMutex.unlock(); + mMutex.lock_shared(); mBlobCache->writeToFile(); + mMutex.unlock_shared(); + mMutex.lock(); } } void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) { ATRACE_NAME("ShaderCache::store"); - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); mNumShadersCachedInRam++; ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam); @@ -204,11 +202,10 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / const void* value = data.data(); - BlobCache* bc = getBlobCacheLocked(); if (mInStoreVkPipelineInProgress) { if (mOldPipelineCacheSize == -1) { // Record the initial pipeline cache size stored in the file. - mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0); + mOldPipelineCacheSize = mBlobCache->get(key.data(), keySize, nullptr, 0); } if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) { // There has not been change in pipeline cache size. Stop trying to save. @@ -223,13 +220,13 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / mNewPipelineCacheSize = -1; mTryToStorePipelineCache = true; } - set(bc, key.data(), keySize, value, valueSize); + set(key.data(), keySize, value, valueSize); if (!mSavePending && mDeferredSaveDelayMs > 0) { mSavePending = true; std::thread deferredSaveThread([this]() { usleep(mDeferredSaveDelayMs * 1000); // milliseconds to microseconds - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); // Store file on disk if there a new shader or Vulkan pipeline cache size changed. if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) { saveToDiskLocked(); @@ -245,11 +242,12 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / void ShaderCache::onVkFrameFlushed(GrDirectContext* context) { { - std::lock_guard<std::mutex> lock(mMutex); - + mMutex.lock_shared(); if (!mInitialized || !mTryToStorePipelineCache) { + mMutex.unlock_shared(); return; } + mMutex.unlock_shared(); } mInStoreVkPipelineInProgress = true; context->storeVkPipelineCacheData(); diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index f5506d60f811..6ccb212fe6ca 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -19,11 +19,14 @@ #include <GrContextOptions.h> #include <SkRefCnt.h> #include <cutils/compiler.h> +#include <ftl/shared_mutex.h> +#include <utils/Mutex.h> + #include <memory> -#include <mutex> #include <string> #include <vector> +class GrDirectContext; class SkData; namespace android { @@ -94,25 +97,23 @@ private: void operator=(const ShaderCache&) = delete; /** - * "getBlobCacheLocked" returns the BlobCache object being used to store the - * key/value blob pairs. If the BlobCache object has not yet been created, - * this will do so, loading the serialized cache contents from disk if - * possible. + * "validateCache" updates the cache to match the given identity. If the + * cache currently has the wrong identity, all entries in the cache are cleared. */ - BlobCache* getBlobCacheLocked(); + bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex); /** - * "validateCache" updates the cache to match the given identity. If the - * cache currently has the wrong identity, all entries in the cache are cleared. + * Helper for BlobCache::set to trace the result and ensure the identity hash + * does not get evicted. */ - bool validateCache(const void* identity, ssize_t size); + void set(const void* key, size_t keySize, const void* value, size_t valueSize) REQUIRES(mMutex); /** - * "saveToDiskLocked" attemps to save the current contents of the cache to + * "saveToDiskLocked" attempts to save the current contents of the cache to * disk. If the identity hash exists, we will insert the identity hash into * the cache for next validation. */ - void saveToDiskLocked(); + void saveToDiskLocked() REQUIRES(mMutex); /** * "mInitialized" indicates whether the ShaderCache is in the initialized @@ -122,16 +123,14 @@ private: * the load and store methods will return without performing any cache * operations. */ - bool mInitialized = false; + bool mInitialized GUARDED_BY(mMutex) = false; /** - * "mBlobCache" is the cache in which the key/value blob pairs are stored. It - * is initially NULL, and will be initialized by getBlobCacheLocked the - * first time it's needed. - * The blob cache contains the Android build number. We treat version mismatches as an empty - * cache (logic implemented in BlobCache::unflatten). + * "mBlobCache" is the cache in which the key/value blob pairs are stored. + * The blob cache contains the Android build number. We treat version mismatches + * as an empty cache (logic implemented in BlobCache::unflatten). */ - std::unique_ptr<FileBlobCache> mBlobCache; + std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex); /** * "mFilename" is the name of the file for storing cache contents in between @@ -140,7 +139,7 @@ private: * empty string indicates that the cache should not be saved to or restored * from disk. */ - std::string mFilename; + std::string mFilename GUARDED_BY(mMutex); /** * "mIDHash" is the current identity hash for the cache validation. It is @@ -149,7 +148,7 @@ private: * indicates that cache validation is not performed, and the hash should * not be stored on disk. */ - std::vector<uint8_t> mIDHash; + std::vector<uint8_t> mIDHash GUARDED_BY(mMutex); /** * "mSavePending" indicates whether or not a deferred save operation is @@ -159,7 +158,7 @@ private: * contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving * is disabled. */ - bool mSavePending = false; + bool mSavePending GUARDED_BY(mMutex) = false; /** * "mObservedBlobValueSize" is the maximum value size observed by the cache reading function. @@ -174,16 +173,16 @@ private: unsigned int mDeferredSaveDelayMs = 4 * 1000; /** - * "mMutex" is the mutex used to prevent concurrent access to the member + * "mMutex" is the shared mutex used to prevent concurrent access to the member * variables. It must be locked whenever the member variables are accessed. */ - mutable std::mutex mMutex; + mutable ftl::SharedMutex mMutex; /** * If set to "true", the next call to onVkFrameFlushed, will invoke * GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk. */ - bool mTryToStorePipelineCache = true; + bool mTryToStorePipelineCache GUARDED_BY(mMutex) = true; /** * This flag is used by "ShaderCache::store" to distinguish between shader data and @@ -195,16 +194,16 @@ private: * "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used * to prevent unnecessary disk writes, if the pipeline cache size has not changed. */ - size_t mNewPipelineCacheSize = -1; + size_t mNewPipelineCacheSize GUARDED_BY(mMutex) = -1; /** * "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk. */ - size_t mOldPipelineCacheSize = -1; + size_t mOldPipelineCacheSize GUARDED_BY(mMutex) = -1; /** * "mCacheDirty" is true when there is new shader cache data, which is not saved to disk. */ - bool mCacheDirty = false; + bool mCacheDirty GUARDED_BY(mMutex) = false; /** * "sCache" is the singleton ShaderCache object. @@ -221,7 +220,7 @@ private: * interesting to keep track of how many shaders are stored in RAM. This * class provides a convenient entry point for that. */ - int mNumShadersCachedInRam = 0; + int mNumShadersCachedInRam GUARDED_BY(mMutex) = 0; friend class ShaderCacheTestUtils; // used for unit testing }; diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index af2d3b34bac7..5c8285a8e1e9 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -66,6 +66,12 @@ void SkiaDisplayList::updateChildren(std::function<void(RenderNode*)> updateFn) } } +void SkiaDisplayList::visit(std::function<void(const RenderNode&)> func) const { + for (auto& child : mChildNodes) { + child.getRenderNode()->visit(func); + } +} + static bool intersects(const SkISize screenSize, const Matrix4& mat, const SkRect& bounds) { Vector3 points[] = { Vector3 {bounds.fLeft, bounds.fTop, 0}, Vector3 {bounds.fRight, bounds.fTop, 0}, diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index 7af31a4dc4c6..e5bd5c9b2a3b 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -145,6 +145,8 @@ public: */ void updateChildren(std::function<void(RenderNode*)> updateFn); + void visit(std::function<void(const RenderNode&)> func) const; + /** * Returns true if there is a child render node that is a projection receiver. */ diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index c4d3f5cedfa8..774478669058 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -16,6 +16,9 @@ #include "SkiaOpenGLPipeline.h" +#include <include/gpu/ganesh/SkSurfaceGanesh.h> +#include <include/gpu/ganesh/gl/GrGLBackendSurface.h> +#include <include/gpu/gl/GrGLTypes.h> #include <GrBackendSurface.h> #include <SkBlendMode.h> #include <SkImageInfo.h> @@ -68,12 +71,15 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { return MakeCurrentResult::AlreadyCurrent; } - // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations + EGLint majorVersion = 0; + eglQueryContext(eglGetCurrentDisplay(), eglGetCurrentContext(), EGL_CONTEXT_CLIENT_VERSION, &majorVersion); + + // Make sure read/draw buffer state of default framebuffer is GL_BACK for ES 3.X. Vendor implementations // disagree on the draw/read buffer state if the default framebuffer transitions from a surface // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic. // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534. // The discussion was not resolved with a clear consensus - if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) { + if (error == 0 && (majorVersion > 2) && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) { GLint curReadFB = 0; GLint curDrawFB = 0; glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB); @@ -135,7 +141,8 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( LOG_ALWAYS_FATAL("Unsupported color type."); } - GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo); + auto backendRT = GrBackendRenderTargets::MakeGL(frame.width(), frame.height(), 0, + STENCIL_BUFFER_SIZE, fboInfo); SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag, kUnknown_SkPixelGeometry); @@ -147,9 +154,9 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( surface = getBufferSkSurface(bufferParams); preTransform = bufferParams.getTransform(); } else { - surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT, - getSurfaceOrigin(), colorType, - mSurfaceColorSpace, &props); + surface = SkSurfaces::WrapBackendRenderTarget(mRenderThread.getGrContext(), backendRT, + getSurfaceOrigin(), colorType, + mSurfaceColorSpace, &props); preTransform = SkMatrix::I(); } @@ -171,7 +178,7 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( { ATRACE_NAME("flush commands"); - surface->flushAndSubmit(); + skgpu::ganesh::FlushAndSubmit(surface); } layerUpdateQueue->clear(); @@ -242,8 +249,7 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh if (mEglSurface != EGL_NO_SURFACE) { const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer); - const bool isPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); - ALOGE_IF(preserveBuffer != isPreserved, "Unable to match the desired swap behavior."); + mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); return true; } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index b020e966e05a..e0f1f6ef44be 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -16,14 +16,16 @@ #include "SkiaPipeline.h" +#include <include/android/SkSurfaceAndroid.h> +#include <include/gpu/ganesh/SkSurfaceGanesh.h> +#include <include/encode/SkPngEncoder.h> #include <SkCanvas.h> #include <SkColor.h> #include <SkColorSpace.h> #include <SkData.h> #include <SkImage.h> -#include <SkImageEncoder.h> +#include <SkImageAndroid.h> #include <SkImageInfo.h> -#include <SkImagePriv.h> #include <SkMatrix.h> #include <SkMultiPictureDocument.h> #include <SkOverdrawCanvas.h> @@ -75,7 +77,7 @@ bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) { return false; } for (SkImage* image : mutableImages) { - if (SkImage_pinAsTexture(image, mRenderThread.getGrContext())) { + if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) { mPinnedImages.emplace_back(sk_ref_sp(image)); } else { return false; @@ -86,7 +88,7 @@ bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) { void SkiaPipeline::unpinImages() { for (auto& image : mPinnedImages) { - SkImage_unpinAsTexture(image.get(), mRenderThread.getGrContext()); + skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get()); } mPinnedImages.clear(); } @@ -187,9 +189,9 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator kPremul_SkAlphaType, getSurfaceColorSpace()); SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); - node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), - skgpu::Budgeted::kYes, info, 0, - this->getSurfaceOrigin(), &props)); + node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(), + 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 // position @@ -200,7 +202,7 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator String8 cachesOutput; mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput, &mRenderThread.renderState()); - ALOGE("%s", cachesOutput.string()); + ALOGE("%s", cachesOutput.c_str()); if (errorHandler) { std::ostringstream err; err << "Unable to create layer for " << node->getName(); @@ -222,8 +224,8 @@ void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height()); auto image = bitmap->makeImage(); if (image.get()) { - SkImage_pinAsTexture(image.get(), context); - SkImage_unpinAsTexture(image.get(), context); + skgpu::ganesh::PinAsTexture(context, image.get()); + skgpu::ganesh::UnpinTexture(context, image.get()); // A submit is necessary as there may not be a frame coming soon, so without a call // to submit these texture uploads can just sit in the queue building up until // we run out of RAM @@ -439,6 +441,13 @@ void SkiaPipeline::endCapture(SkSurface* surface) { procs.fTypefaceProc = [](SkTypeface* tf, void* ctx){ return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData); }; + procs.fImageProc = [](SkImage* img, void* ctx) -> sk_sp<SkData> { + GrDirectContext* dCtx = static_cast<GrDirectContext*>(ctx); + return SkPngEncoder::Encode(dCtx, + img, + SkPngEncoder::Options{}); + }; + procs.fImageCtx = mRenderThread.getGrContext(); auto data = picture->serialize(&procs); savePictureAsync(data, mCapturedFile); mCaptureSequence = 0; @@ -621,7 +630,7 @@ sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface( auto bufferColorSpace = bufferParams.getColorSpace(); if (mBufferSurface == nullptr || mBufferColorSpace == nullptr || !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) { - mBufferSurface = SkSurface::MakeFromAHardwareBuffer( + mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer( mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin, bufferColorSpace, nullptr, true); mBufferColorSpace = bufferColorSpace; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 3ca7eeb37a89..e917f9a66917 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -37,6 +37,7 @@ #include "NinePatchUtils.h" #include "RenderNode.h" #include "pipeline/skia/AnimatedDrawables.h" +#include "pipeline/skia/BackdropFilterDrawable.h" #ifdef __ANDROID__ // Layoutlib does not support GL, Vulcan etc. #include "pipeline/skia/GLFunctorDrawable.h" #include "pipeline/skia/VkFunctorDrawable.h" @@ -168,6 +169,14 @@ void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) { // Put Vulkan WebViews with non-rectangular clips in a HW layer renderNode->mutateStagingProperties().setClipMayBeComplex(mRecorder.isClipMayBeComplex()); } + + // draw backdrop filter drawable if needed. + if (renderNode->stagingProperties().layerProperties().getBackdropImageFilter()) { + auto* backdropFilterDrawable = + mDisplayList->allocateDrawable<BackdropFilterDrawable>(renderNode, asSkCanvas()); + drawDrawable(backdropFilterDrawable); + } + drawDrawable(&renderNodeDrawable); // use staging property, since recording on UI thread @@ -227,6 +236,17 @@ void SkiaRecordingCanvas::handleMutableImages(Bitmap& bitmap, DrawImagePayload& } } +void SkiaRecordingCanvas::onFilterPaint(android::Paint& paint) { + INHERITED::onFilterPaint(paint); + SkShader* shader = paint.getShader(); + // TODO(b/264559422): This only works for very specifically a BitmapShader. + // It's better than nothing, though + SkImage* image = shader ? shader->isAImage(nullptr, nullptr) : nullptr; + if (image) { + mDisplayList->mMutableImages.push_back(image); + } +} + void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) { auto payload = DrawImagePayload(bitmap); diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index a8e4580dc200..3bd091df1ece 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -105,6 +105,8 @@ private: void handleMutableImages(Bitmap& bitmap, DrawImagePayload& payload); + void onFilterPaint(Paint& paint) override; + using INHERITED = SkiaCanvas; }; diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp index cad3703d8d2b..1676787ef671 100644 --- a/libs/hwui/pipeline/skia/StretchMask.cpp +++ b/libs/hwui/pipeline/skia/StretchMask.cpp @@ -18,14 +18,13 @@ #include "SkBlendMode.h" #include "SkCanvas.h" #include "SkSurface.h" -#include "include/gpu/GpuTypes.h" // from Skia #include "TransformCanvas.h" #include "SkiaDisplayList.h" using android::uirenderer::StretchMask; -void StretchMask::draw(GrRecordingContext* context, +void StretchMask::draw(GrRecordingContext*, const StretchEffect& stretch, const SkRect& bounds, skiapipeline::SkiaDisplayList* displayList, @@ -35,16 +34,14 @@ void StretchMask::draw(GrRecordingContext* context, if (mMaskSurface == nullptr || mMaskSurface->width() != width || mMaskSurface->height() != height) { // Create a new surface if we don't have one or our existing size does - // not match. - mMaskSurface = SkSurface::MakeRenderTarget( - context, - skgpu::Budgeted::kYes, - SkImageInfo::Make( - width, - height, - SkColorType::kAlpha_8_SkColorType, - SkAlphaType::kPremul_SkAlphaType) - ); + // not match. SkCanvas::makeSurface returns a new surface that will + // be GPU-backed if canvas was also. + mMaskSurface = canvas->makeSurface(SkImageInfo::Make( + width, + height, + SkColorType::kAlpha_8_SkColorType, + SkAlphaType::kPremul_SkAlphaType + )); mIsDirty = true; } @@ -53,7 +50,7 @@ void StretchMask::draw(GrRecordingContext* context, // Make sure to apply target transformation to the mask canvas // to ensure the replayed drawing commands generate the same result auto previousMatrix = displayList->mParentMatrix; - displayList->mParentMatrix = maskCanvas->getTotalMatrix(); + displayList->mParentMatrix = maskCanvas->getLocalToDeviceAs3x3(); maskCanvas->save(); maskCanvas->drawColor(0, SkBlendMode::kClear); TransformCanvas transformCanvas(maskCanvas, SkBlendMode::kSrcOver); diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index adf3c06b8624..475b110604e4 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -35,6 +35,8 @@ #include "effects/GainmapRenderer.h" #include <SkBlendMode.h> +#include <SkImage.h> +#include <SkImageAndroid.h> namespace android { namespace uirenderer { @@ -183,9 +185,9 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { // drawing into the offscreen surface, so we need to reset it here. canvas->resetMatrix(); - auto functorImage = SkImage::MakeFromAHardwareBuffer(mFrameBuffer.get(), kPremul_SkAlphaType, - canvas->imageInfo().refColorSpace(), - kBottomLeft_GrSurfaceOrigin); + auto functorImage = SkImages::DeferredFromAHardwareBuffer( + mFrameBuffer.get(), kPremul_SkAlphaType, canvas->imageInfo().refColorSpace(), + kBottomLeft_GrSurfaceOrigin); canvas->drawImage(functorImage, 0, 0, SkSamplingOptions(), &paint); canvas->restore(); } diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h index eb1f9304a5c8..ed3fabc61708 100644 --- a/libs/hwui/private/hwui/DrawGlInfo.h +++ b/libs/hwui/private/hwui/DrawGlInfo.h @@ -24,8 +24,7 @@ namespace android { namespace uirenderer { /** - * Structure used by OpenGLRenderer::callDrawGLFunction() to pass and - * receive data from OpenGL functors. + * Structure used to pass and receive data from OpenGL functors. */ struct DrawGlInfo { // Input: current clip rect diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index babce88b8e1e..735fc074435d 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -17,6 +17,7 @@ #include "CacheManager.h" #include <GrContextOptions.h> +#include <GrTypes.h> #include <SkExecutor.h> #include <SkGraphics.h> #include <math.h> @@ -110,13 +111,18 @@ void CacheManager::configureContext(GrContextOptions* contextOptions, const void contextOptions->fPersistentCache = &cache; } +static GrPurgeResourceOptions toSkiaEnum(bool scratchOnly) { + return scratchOnly ? GrPurgeResourceOptions::kScratchResourcesOnly : + GrPurgeResourceOptions::kAllResources; +} + void CacheManager::trimMemory(TrimLevel mode) { if (!mGrContext) { return; } // flush and submit all work to the gpu and wait for it to finish - mGrContext->flushAndSubmit(/*syncCpu=*/true); + mGrContext->flushAndSubmit(GrSyncCpu::kYes); switch (mode) { case TrimLevel::BACKGROUND: @@ -130,7 +136,7 @@ void CacheManager::trimMemory(TrimLevel mode) { // that have persistent data to be purged in LRU order. mGrContext->setResourceCacheLimit(mBackgroundResourceBytes); SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes); - mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly); + mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly)); mGrContext->setResourceCacheLimit(mMaxResourceBytes); SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); break; @@ -150,7 +156,7 @@ void CacheManager::trimCaches(CacheTrimLevel mode) { case CacheTrimLevel::ALL_CACHES: SkGraphics::PurgeAllCaches(); if (mGrContext) { - mGrContext->purgeUnlockedResources(false); + mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources); } break; default: @@ -277,14 +283,15 @@ void CacheManager::onThreadIdle() { const nsecs_t now = systemTime(CLOCK_MONOTONIC); // Rate limiting - if ((now - mLastDeferredCleanup) < 25_ms) { + 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)); + ns2ms(std::clamp(frameDiffNanos, mMemoryPolicy.minimumResourceRetention, + mMemoryPolicy.maximumResourceRetention)); mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis), - mMemoryPolicy.purgeScratchOnly); + toSkiaEnum(mMemoryPolicy.purgeScratchOnly)); } } diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index 5e43ac209696..bcfa4f359d83 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -64,12 +64,13 @@ public: void unregisterCanvasContext(CanvasContext* context); void onContextStopped(CanvasContext* context); + bool areAllContextsStopped(); + private: friend class RenderThread; explicit CacheManager(RenderThread& thread); void setupCacheLimits(); - bool areAllContextsStopped(); void checkUiHidden(); void scheduleDestroyContext(); void cancelDestroyContext(); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 16b35ffcabac..d54c5f57c95c 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -125,6 +125,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* , mRenderPipeline(std::move(renderPipeline)) , mHintSessionWrapper(uiThreadId, renderThreadId) { mRenderThread.cacheManager().registerCanvasContext(this); + mRenderThread.renderState().registerContextCallback(this); rootRenderNode->makeRoot(); mRenderNodes.emplace_back(rootRenderNode); mProfiler.setDensity(DeviceInfo::getDensity()); @@ -137,6 +138,7 @@ CanvasContext::~CanvasContext() { } mRenderNodes.clear(); mRenderThread.cacheManager().unregisterCanvasContext(this); + mRenderThread.renderState().removeContextCallback(this); } void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) { @@ -193,6 +195,7 @@ void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) { void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) { ATRACE_CALL(); + startHintSession(); if (window) { mNativeSurface = std::make_unique<ReliableSurface>(window); mNativeSurface->init(); @@ -355,8 +358,9 @@ bool CanvasContext::makeCurrent() { return true; } -static bool wasSkipped(FrameInfo* info) { - return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame); +static std::optional<SkippedFrameReason> wasSkipped(FrameInfo* info) { + if (info) return info->getSkippedFrameReason(); + return std::nullopt; } bool CanvasContext::isSwapChainStuffed() { @@ -405,8 +409,30 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy // If the previous frame was dropped we don't need to hold onto it, so // just keep using the previous frame's structure instead - if (!wasSkipped(mCurrentFrameInfo)) { + if (const auto reason = wasSkipped(mCurrentFrameInfo)) { + // Use the oldest skipped frame in case we skip more than a single frame + if (!mSkippedFrameInfo) { + switch (*reason) { + case SkippedFrameReason::AlreadyDrawn: + case SkippedFrameReason::NoBuffer: + case SkippedFrameReason::NoOutputTarget: + mSkippedFrameInfo.emplace(); + mSkippedFrameInfo->vsyncId = + mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId); + mSkippedFrameInfo->startTime = + mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime); + break; + case SkippedFrameReason::DrawingOff: + case SkippedFrameReason::ContextIsStopped: + case SkippedFrameReason::NothingToDraw: + // Do not report those as skipped frames as there was no frame expected to be + // drawn + break; + } + } + } else { mCurrentFrameInfo = mJankTracker.startFrame(); + mSkippedFrameInfo.reset(); } mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo); @@ -416,7 +442,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy info.damageAccumulator = &mDamageAccumulator; info.layerUpdateQueue = &mLayerUpdateQueue; info.damageGenerationId = mDamageId++; - info.out.canDrawThisFrame = true; + info.out.skippedFrameReason = std::nullopt; mAnimationContext->startFrame(info.mode); for (const sp<RenderNode>& node : mRenderNodes) { @@ -436,8 +462,8 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mIsDirty = true; if (CC_UNLIKELY(!hasOutputTarget())) { - mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); - info.out.canDrawThisFrame = false; + info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget; + mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason); return; } @@ -452,23 +478,23 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy if (vsyncDelta < 2_ms) { // Already drew for this vsync pulse, UI draw request missed // the deadline for RT animations - info.out.canDrawThisFrame = false; + info.out.skippedFrameReason = SkippedFrameReason::AlreadyDrawn; } } else { - info.out.canDrawThisFrame = true; + info.out.skippedFrameReason = std::nullopt; } // TODO: Do we need to abort out if the backdrop is added but not ready? Should that even // be an allowable combination? if (mRenderNodes.size() > 2 && !mRenderNodes[1]->isRenderable()) { - info.out.canDrawThisFrame = false; + info.out.skippedFrameReason = SkippedFrameReason::NothingToDraw; } - if (info.out.canDrawThisFrame) { + if (!info.out.skippedFrameReason) { int err = mNativeSurface->reserveNext(); if (err != OK) { - mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); - info.out.canDrawThisFrame = false; + info.out.skippedFrameReason = SkippedFrameReason::NoBuffer; + mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason); ALOGW("reserveNext failed, error = %d (%s)", err, strerror(-err)); if (err != TIMED_OUT) { // A timed out surface can still recover, but assume others are permanently dead. @@ -477,11 +503,11 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy } } } else { - mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); + mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason); } bool postedFrameCallback = false; - if (info.out.hasAnimations || !info.out.canDrawThisFrame) { + if (info.out.hasAnimations || info.out.skippedFrameReason) { if (CC_UNLIKELY(!Properties::enableRTAnimations)) { info.out.requiresUiRedraw = true; } @@ -547,9 +573,20 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { mSyncDelayDuration = 0; mIdleDuration = 0; - if (!Properties::isDrawingEnabled() || - (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw())) { - mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); + const auto skippedFrameReason = [&]() -> std::optional<SkippedFrameReason> { + if (!Properties::isDrawingEnabled()) { + return SkippedFrameReason::DrawingOff; + } + + if (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw()) { + return SkippedFrameReason::NothingToDraw; + } + + return std::nullopt; + }(); + if (skippedFrameReason) { + mCurrentFrameInfo->setSkippedFrameReason(*skippedFrameReason); + if (auto grContext = getGrContext()) { // Submit to ensure that any texture uploads complete and Skia can // free its staging buffers. @@ -602,10 +639,18 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) { const auto inputEventId = static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId)); - native_window_set_frame_timeline_info( - mNativeSurface->getNativeWindow(), frameCompleteNr, vsyncId, inputEventId, - mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime), - solelyTextureViewUpdates); + const ANativeWindowFrameTimelineInfo ftl = { + .frameNumber = frameCompleteNr, + .frameTimelineVsyncId = vsyncId, + .inputEventId = inputEventId, + .startTimeNanos = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime), + .useForRefreshRateSelection = solelyTextureViewUpdates, + .skippedFrameVsyncId = mSkippedFrameInfo ? mSkippedFrameInfo->vsyncId + : UiFrameInfoBuilder::INVALID_VSYNC_ID, + .skippedFrameStartTimeNanos = + mSkippedFrameInfo ? mSkippedFrameInfo->startTime : 0, + }; + native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), ftl); } } @@ -885,7 +930,7 @@ void CanvasContext::prepareAndDraw(RenderNode* node) { TreeInfo info(TreeInfo::MODE_RT_ONLY, *this); prepareTree(info, frameInfo, systemTime(SYSTEM_TIME_MONOTONIC), node); - if (info.out.canDrawThisFrame) { + if (!info.out.skippedFrameReason) { draw(info.out.solelyTextureViewUpdates); } else { // wait on fences so tasks don't overlap next frame @@ -946,6 +991,10 @@ void CanvasContext::destroyHardwareResources() { } } +void CanvasContext::onContextDestroyed() { + destroyHardwareResources(); +} + DeferredLayerUpdater* CanvasContext::createTextureLayer() { return mRenderPipeline->createTextureLayer(); } @@ -1084,6 +1133,12 @@ bool CanvasContext::shouldDither() { return self->mColorMode != ColorMode::Default; } +void CanvasContext::visitAllRenderNodes(std::function<void(const RenderNode&)> func) const { + for (auto node : mRenderNodes) { + node->visit(func); + } +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 5219b5757008..241f8dd879d9 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -43,6 +43,7 @@ #include "Lighting.h" #include "ReliableSurface.h" #include "RenderNode.h" +#include "renderstate/RenderState.h" #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" #include "utils/RingBuffer.h" @@ -64,7 +65,7 @@ class Frame; // This per-renderer class manages the bridge between the global EGL context // and the render surface. // TODO: Rename to Renderer or some other per-window, top-level manager -class CanvasContext : public IFrameCallback { +class CanvasContext : public IFrameCallback, public IGpuContextCallback { public: static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory, pid_t uiThreadId, @@ -154,6 +155,7 @@ public: void markLayerInUse(RenderNode* node); void destroyHardwareResources(); + void onContextDestroyed() override; DeferredLayerUpdater* createTextureLayer(); @@ -236,6 +238,8 @@ public: static bool shouldDither(); + void visitAllRenderNodes(std::function<void(const RenderNode&)>) const; + private: CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline, @@ -366,6 +370,12 @@ private: ColorMode mColorMode = ColorMode::Default; float mTargetSdrHdrRatio = 1.f; + + struct SkippedFrameInfo { + int64_t vsyncId; + int64_t startTime; + }; + std::optional<SkippedFrameInfo> mSkippedFrameInfo; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 53b43ba417d0..1b333bfccbf1 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -104,7 +104,7 @@ void DrawFrameTask::run() { info.forceDrawFrame = mForceDrawFrame; mForceDrawFrame = false; canUnblockUiThread = syncFrameState(info); - canDrawThisFrame = info.out.canDrawThisFrame; + canDrawThisFrame = !info.out.skippedFrameReason.has_value(); solelyTextureViewUpdates = info.out.solelyTextureViewUpdates; if (mFrameCommitCallback) { @@ -192,11 +192,12 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { if (CC_UNLIKELY(!hasTarget || !canDraw)) { if (!hasTarget) { mSyncResult |= SyncResult::LostSurfaceRewardIfFound; + info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget; } else { // If we have a surface but can't draw we must be stopped mSyncResult |= SyncResult::ContextIsStopped; + info.out.skippedFrameReason = SkippedFrameReason::ContextIsStopped; } - info.out.canDrawThisFrame = false; } if (info.out.hasAnimations) { @@ -204,7 +205,7 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { mSyncResult |= SyncResult::UIRedrawRequired; } } - if (!info.out.canDrawThisFrame) { + if (info.out.skippedFrameReason) { mSyncResult |= SyncResult::FrameDropped; } // If prepareTextures is false, we ran out of texture cache space diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp index 30e472a79926..b34da5153a72 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.cpp +++ b/libs/hwui/renderthread/HintSessionWrapper.cpp @@ -32,72 +32,41 @@ 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; +#define BIND_APH_METHOD(name) \ + name = (decltype(name))dlsym(handle_, "APerformanceHint_" #name); \ + LOG_ALWAYS_FATAL_IF(name == nullptr, "Failed to find required symbol APerformanceHint_" #name) + +void HintSessionWrapper::HintSessionBinding::init() { + if (mInitialized) 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!"); + BIND_APH_METHOD(getManager); + BIND_APH_METHOD(createSession); + BIND_APH_METHOD(closeSession); + BIND_APH_METHOD(updateTargetWorkDuration); + BIND_APH_METHOD(reportActualWorkDuration); + BIND_APH_METHOD(sendHint); - gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint"); - LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr, - "Failed to find required symbol APerformanceHint_sendHint!"); - - gAPerformanceHintBindingInitialized = true; + mInitialized = true; } -} // namespace - HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId) - : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {} + : mUiThreadId(uiThreadId) + , mRenderThreadId(renderThreadId) + , mBinding(std::make_shared<HintSessionBinding>()) {} HintSessionWrapper::~HintSessionWrapper() { + destroy(); +} + +void HintSessionWrapper::destroy() { if (mHintSessionFuture.valid()) { mHintSession = mHintSessionFuture.get(); } if (mHintSession) { - gAPH_closeSessionFn(mHintSession); + mBinding->closeSession(mHintSession); mSessionValid = true; mHintSession = nullptr; } @@ -129,9 +98,9 @@ bool HintSessionWrapper::init() { // Assume that if we return before the end, it broke mSessionValid = false; - ensureAPerformanceHintBindingInitialized(); + mBinding->init(); - APerformanceHintManager* manager = gAPH_getManagerFn(); + APerformanceHintManager* manager = mBinding->getManager(); if (!manager) return false; std::vector<pid_t> tids = CommonPool::getThreadIds(); @@ -141,8 +110,9 @@ bool HintSessionWrapper::init() { // Use a placeholder target value to initialize, // this will always be replaced elsewhere before it gets used int64_t defaultTargetDurationNanos = 16666667; - mHintSessionFuture = CommonPool::async([=, tids = std::move(tids)] { - return gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos); + mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] { + return mBinding->createSession(manager, tids.data(), tids.size(), + defaultTargetDurationNanos); }); return false; } @@ -154,7 +124,7 @@ void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) targetWorkDurationNanos > kSanityCheckLowerBound && targetWorkDurationNanos < kSanityCheckUpperBound) { mLastTargetWorkDuration = targetWorkDurationNanos; - gAPH_updateTargetWorkDurationFn(mHintSession, targetWorkDurationNanos); + mBinding->updateTargetWorkDuration(mHintSession, targetWorkDurationNanos); } mLastFrameNotification = systemTime(); } @@ -164,7 +134,7 @@ void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) { mResetsSinceLastReport = 0; if (actualDurationNanos > kSanityCheckLowerBound && actualDurationNanos < kSanityCheckUpperBound) { - gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos); + mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos); } } @@ -175,14 +145,14 @@ void HintSessionWrapper::sendLoadResetHint() { if (now - mLastFrameNotification > kResetHintTimeout && mResetsSinceLastReport <= kMaxResetsSinceLastReport) { ++mResetsSinceLastReport; - gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET)); + mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET)); } mLastFrameNotification = now; } void HintSessionWrapper::sendLoadIncreaseHint() { if (!init()) return; - gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_UP)); + mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)); } } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h index 24b8150dd489..f8b876e28d51 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.h +++ b/libs/hwui/renderthread/HintSessionWrapper.h @@ -29,6 +29,8 @@ namespace renderthread { class HintSessionWrapper { public: + friend class HintSessionWrapperTests; + HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId); ~HintSessionWrapper(); @@ -37,6 +39,7 @@ public: void sendLoadResetHint(); void sendLoadIncreaseHint(); bool init(); + void destroy(); private: APerformanceHintSession* mHintSession = nullptr; @@ -54,6 +57,28 @@ private: static constexpr nsecs_t kResetHintTimeout = 100_ms; static constexpr int64_t kSanityCheckLowerBound = 100_us; static constexpr int64_t kSanityCheckUpperBound = 10_s; + + // Allows easier stub when testing + class HintSessionBinding { + public: + virtual ~HintSessionBinding() = default; + virtual void init(); + APerformanceHintManager* (*getManager)(); + APerformanceHintSession* (*createSession)(APerformanceHintManager* manager, + const int32_t* tids, size_t tidCount, + int64_t defaultTarget) = nullptr; + void (*closeSession)(APerformanceHintSession* session) = nullptr; + void (*updateTargetWorkDuration)(APerformanceHintSession* session, + int64_t targetDuration) = nullptr; + void (*reportActualWorkDuration)(APerformanceHintSession* session, + int64_t actualDuration) = nullptr; + void (*sendHint)(APerformanceHintSession* session, int32_t hintId) = nullptr; + + private: + bool mInitialized = false; + }; + + std::shared_ptr<HintSessionBinding> mBinding; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 224c878bf43d..be163bad77a7 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -16,7 +16,13 @@ #include "RenderProxy.h" +#include <SkBitmap.h> +#include <SkImage.h> +#include <SkPicture.h> #include <gui/TraceUtils.h> +#include <pthread.h> +#include <ui/GraphicBufferAllocator.h> + #include "DeferredLayerUpdater.h" #include "DisplayList.h" #include "Properties.h" @@ -29,12 +35,6 @@ #include "utils/Macros.h" #include "utils/TimeUtils.h" -#include <SkBitmap.h> -#include <SkImage.h> -#include <SkPicture.h> - -#include <pthread.h> - namespace android { namespace uirenderer { namespace renderthread { @@ -323,6 +323,11 @@ void RenderProxy::dumpGraphicsMemory(int fd, bool includeProfileData, bool reset } }); } + if (!Properties::isolatedProcess) { + std::string grallocInfo; + GraphicBufferAllocator::getInstance().dump(grallocInfo); + dprintf(fd, "%s\n", grallocInfo.c_str()); + } } void RenderProxy::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) { diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index eb28c080c056..94ed06c806e5 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -357,7 +357,15 @@ void RenderThread::dumpGraphicsMemory(int fd, bool includeProfileData) { String8 cachesOutput; mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState); - dprintf(fd, "\nPipeline=%s\n%s\n", pipelineToString(), cachesOutput.string()); + dprintf(fd, "\nPipeline=%s\n%s", pipelineToString(), cachesOutput.c_str()); + for (auto&& context : mCacheManager->mCanvasContexts) { + context->visitAllRenderNodes([&](const RenderNode& node) { + if (node.isTextureView()) { + dprintf(fd, "TextureView: %dx%d\n", node.getWidth(), node.getHeight()); + } + }); + } + dprintf(fd, "\n"); } void RenderThread::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) { diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 46698a6fdcc0..90850d36b612 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -24,6 +24,8 @@ #include <GrTypes.h> #include <android/sync.h> #include <gui/TraceUtils.h> +#include <include/gpu/ganesh/SkSurfaceGanesh.h> +#include <include/gpu/ganesh/vk/GrVkBackendSurface.h> #include <ui/FatVector.h> #include <vk/GrVkExtensions.h> #include <vk/GrVkTypes.h> @@ -33,9 +35,6 @@ #include "pipeline/skia/ShaderCache.h" #include "renderstate/RenderState.h" -#undef LOG_TAG -#define LOG_TAG "VulkanManager" - namespace android { namespace uirenderer { namespace renderthread { @@ -386,25 +385,23 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe } void VulkanManager::initialize() { - std::lock_guard _lock{mInitializeLock}; - - if (mDevice != VK_NULL_HANDLE) { - return; - } + std::call_once(mInitFlag, [&] { + GET_PROC(EnumerateInstanceVersion); + uint32_t instanceVersion; + LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion)); + LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0)); - GET_PROC(EnumerateInstanceVersion); - uint32_t instanceVersion; - LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion)); - LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0)); + this->setupDevice(mExtensions, mPhysicalDeviceFeatures2); - this->setupDevice(mExtensions, mPhysicalDeviceFeatures2); + mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue); + mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue); - mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue); - mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue); + if (Properties::enablePartialUpdates && Properties::useBufferAge) { + mSwapBehavior = SwapBehavior::BufferAge; + } - if (Properties::enablePartialUpdates && Properties::useBufferAge) { - mSwapBehavior = SwapBehavior::BufferAge; - } + mInitialized = true; + }); } static void onGrContextReleased(void* context) { @@ -518,7 +515,7 @@ Frame VulkanManager::dequeueNextBuffer(VulkanSurface* surface) { // The following flush blocks the GPU immediately instead of waiting for // other drawing ops. It seems dequeue_fence is not respected otherwise. // TODO: remove the flush after finding why backendSemaphore is not working. - bufferInfo->skSurface->flushAndSubmit(); + skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface.get()); } } } @@ -586,10 +583,10 @@ nsecs_t VulkanManager::finishFrame(SkSurface* surface) { } else { semaphore = VK_NULL_HANDLE; } - GrSemaphoresSubmitted submitted = - surface->flush(SkSurface::BackendSurfaceAccess::kPresent, flushInfo); GrDirectContext* context = GrAsDirectContext(surface->recordingContext()); ALOGE_IF(!context, "Surface is not backed by gpu"); + GrSemaphoresSubmitted submitted = context->flush( + surface, SkSurfaces::BackendSurfaceAccess::kPresent, flushInfo); context->submit(); const nsecs_t submissionTime = systemTime(); if (semaphore != VK_NULL_HANDLE) { @@ -599,10 +596,11 @@ nsecs_t VulkanManager::finishFrame(SkSurface* surface) { // retrieve VkImage used as render target VkImage image = VK_NULL_HANDLE; GrBackendRenderTarget backendRenderTarget = - surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess); + SkSurfaces::GetBackendRenderTarget( + surface, SkSurfaces::BackendHandleAccess::kFlushRead); if (backendRenderTarget.isValid()) { GrVkImageInfo info; - if (backendRenderTarget.getVkImageInfo(&info)) { + if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) { image = info.fImage; } else { ALOGE("Frame boundary: backend is not vulkan"); diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index 2be1ffdbc423..dbef7fbd51b2 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -70,7 +70,7 @@ public: void initialize(); // Quick check to see if the VulkanManager has been initialized. - bool hasVkContext() { return mDevice != VK_NULL_HANDLE; } + bool hasVkContext() { return mInitialized; } // Create and destroy functions for wrapping an ANativeWindow in a VulkanSurface VulkanSurface* createSurface(ANativeWindow* window, @@ -204,7 +204,8 @@ private: VkSemaphore mSwapSemaphore = VK_NULL_HANDLE; void* mDestroySemaphoreContext = nullptr; - std::mutex mInitializeLock; + std::once_flag mInitFlag; + std::atomic_bool mInitialized = false; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index 3168cb09291b..20b743bab2c2 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -16,6 +16,7 @@ #include "VulkanSurface.h" +#include <include/android/SkSurfaceAndroid.h> #include <GrDirectContext.h> #include <SkSurface.h> #include <algorithm> @@ -24,9 +25,6 @@ #include "VulkanManager.h" #include "utils/Color.h" -#undef LOG_TAG -#define LOG_TAG "VulkanSurface" - namespace android { namespace uirenderer { namespace renderthread { @@ -470,12 +468,12 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(), surfaceProps.pixelGeometry()); } - bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer( + bufferInfo->skSurface = SkSurfaces::WrapAndroidHardwareBuffer( mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()), kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps, /*from_window=*/true); if (bufferInfo->skSurface.get() == nullptr) { - ALOGE("SkSurface::MakeFromAHardwareBuffer failed"); + ALOGE("SkSurfaces::WrapAndroidHardwareBuffer failed"); mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, mNativeBuffers[idx].dequeue_fence.release()); mNativeBuffers[idx].dequeued = false; diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 81ecfe59d3bc..ffc664c2e1bc 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -61,18 +61,10 @@ namespace uirenderer { ADD_FAILURE() << "ClipState not a rect"; \ } -#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \ - TEST(test_case_name, test_name##_##pipeline) { \ - RenderPipelineType oldType = Properties::getRenderPipelineType(); \ - Properties::overrideRenderPipelineType(RenderPipelineType::pipeline); \ - functionCall; \ - Properties::overrideRenderPipelineType(oldType); \ - }; - -#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \ - INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, \ - TestUtils::runOnRenderThread( \ - test_case_name##_##test_name##_RenderThreadTest::doTheThing)) +#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name) \ + TEST(test_case_name, test_name) { \ + TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \ + } /** * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope @@ -83,21 +75,7 @@ namespace uirenderer { public: \ static void doTheThing(renderthread::RenderThread& renderThread); \ }; \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ - /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \ - /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \ - void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ - renderthread::RenderThread& renderThread) - -/** - * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes - */ -#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \ - class test_case_name##_##test_name##_RenderThreadTest { \ - public: \ - static void doTheThing(renderthread::RenderThread& renderThread); \ - }; \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ + INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name); \ /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \ /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \ void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ @@ -307,13 +285,21 @@ public: int destroyed = 0; int removeOverlays = 0; int glesDraw = 0; + int vkInitialize = 0; + int vkDraw = 0; + int vkPostDraw = 0; }; static void expectOnRenderThread(const std::string_view& function = "unknown") { EXPECT_EQ(gettid(), TestUtils::getRenderThreadTid()) << "Called on wrong thread: " << function; } - static WebViewFunctorCallbacks createMockFunctor(RenderMode mode) { + static int createMockFunctor() { + const auto renderMode = WebViewFunctor_queryPlatformRenderMode(); + return WebViewFunctor_create(nullptr, createMockFunctorCallbacks(renderMode), renderMode); + } + + static WebViewFunctorCallbacks createMockFunctorCallbacks(RenderMode mode) { auto callbacks = WebViewFunctorCallbacks{ .onSync = [](int functor, void* client_data, const WebViewSyncData& data) { @@ -345,9 +331,22 @@ public: sMockFunctorCounts[functor].glesDraw++; }; break; - default: - ADD_FAILURE(); - return WebViewFunctorCallbacks{}; + case RenderMode::Vulkan: + callbacks.vk.initialize = [](int functor, void* data, + const VkFunctorInitParams& params) { + expectOnRenderThread("initialize"); + sMockFunctorCounts[functor].vkInitialize++; + }; + callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params, + const WebViewOverlayData& overlayParams) { + expectOnRenderThread("draw"); + sMockFunctorCounts[functor].vkDraw++; + }; + callbacks.vk.postDraw = [](int functor, void* data) { + expectOnRenderThread("postDraw"); + sMockFunctorCounts[functor].vkPostDraw++; + }; + break; } return callbacks; } diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp index f3f32eb6897c..e227999c2432 100644 --- a/libs/hwui/tests/macrobench/main.cpp +++ b/libs/hwui/tests/macrobench/main.cpp @@ -14,30 +14,32 @@ * limitations under the License. */ -#include "tests/common/LeakChecker.h" -#include "tests/common/TestScene.h" - -#include "Properties.h" -#include "hwui/Typeface.h" -#include "HardwareBitmapUploader.h" -#include "renderthread/RenderProxy.h" - +#include <android-base/parsebool.h> #include <benchmark/benchmark.h> +#include <errno.h> +#include <fcntl.h> #include <fnmatch.h> #include <getopt.h> #include <pthread.h> #include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> #include <unistd.h> + +#include <regex> #include <string> #include <unordered_map> #include <vector> -#include <errno.h> -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/types.h> +#include "HardwareBitmapUploader.h" +#include "Properties.h" +#include "hwui/Typeface.h" +#include "renderthread/RenderProxy.h" +#include "tests/common/LeakChecker.h" +#include "tests/common/TestScene.h" using namespace android; +using namespace android::base; using namespace android::uirenderer; using namespace android::uirenderer::test; @@ -69,6 +71,9 @@ OPTIONS: --onscreen Render tests on device screen. By default tests are offscreen rendered --benchmark_format Set output format. Possible values are tabular, json, csv + --benchmark_list_tests Lists the tests that would run but does not run them + --benchmark_filter=<regex> Filters the test set to the given regex. If prefixed with `-` and test + that doesn't match the given regex is run --renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk --skip-leak-check Skips the memory leak check --report-gpu-memory[=verbose] Dumps the GPU memory usage after each test run @@ -140,6 +145,9 @@ static bool setBenchmarkFormat(const char* format) { if (!strcmp(format, "tabular")) { gBenchmarkReporter.reset(new benchmark::ConsoleReporter()); } else if (!strcmp(format, "json")) { + // We cannot print the leak check if outputing to JSON as that will break + // JSON parsers since it's not JSON-formatted + gRunLeakCheck = false; gBenchmarkReporter.reset(new benchmark::JSONReporter()); } else { fprintf(stderr, "Unknown format '%s'\n", format); @@ -160,6 +168,24 @@ static bool setRenderer(const char* renderer) { return true; } +static void addTestsThatMatchFilter(std::string spec) { + if (spec.empty() || spec == "all") { + spec = "."; // Regexp that matches all benchmarks + } + bool isNegativeFilter = false; + if (spec[0] == '-') { + spec.replace(0, 1, ""); + isNegativeFilter = true; + } + std::regex re(spec, std::regex_constants::extended); + for (auto& iter : TestScene::testMap()) { + if ((isNegativeFilter && !std::regex_search(iter.first, re)) || + (!isNegativeFilter && std::regex_search(iter.first, re))) { + gRunTests.push_back(iter.second); + } + } +} + // For options that only exist in long-form. Anything in the // 0-255 range is reserved for short options (which just use their ASCII value) namespace LongOpts { @@ -170,6 +196,8 @@ enum { ReportFrametime, CpuSet, BenchmarkFormat, + BenchmarkListTests, + BenchmarkFilter, Onscreen, Offscreen, Renderer, @@ -179,14 +207,16 @@ enum { } static const struct option LONG_OPTIONS[] = { - {"frames", required_argument, nullptr, 'f'}, - {"repeat", required_argument, nullptr, 'r'}, + {"count", required_argument, nullptr, 'c'}, + {"runs", required_argument, nullptr, 'r'}, {"help", no_argument, nullptr, 'h'}, {"list", no_argument, nullptr, LongOpts::List}, {"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu}, {"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime}, {"cpuset", required_argument, nullptr, LongOpts::CpuSet}, {"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat}, + {"benchmark_list_tests", optional_argument, nullptr, LongOpts::BenchmarkListTests}, + {"benchmark_filter", required_argument, nullptr, LongOpts::BenchmarkFilter}, {"onscreen", no_argument, nullptr, LongOpts::Onscreen}, {"offscreen", no_argument, nullptr, LongOpts::Offscreen}, {"renderer", required_argument, nullptr, LongOpts::Renderer}, @@ -197,8 +227,12 @@ static const struct option LONG_OPTIONS[] = { static const char* SHORT_OPTIONS = "c:r:h"; void parseOptions(int argc, char* argv[]) { + benchmark::BenchmarkReporter::Context::executable_name = (argc > 0) ? argv[0] : "unknown"; + int c; bool error = false; + bool listTestsOnly = false; + bool testsAreFiltered = false; opterr = 0; while (true) { @@ -272,6 +306,21 @@ void parseOptions(int argc, char* argv[]) { } break; + case LongOpts::BenchmarkListTests: + if (!optarg || ParseBool(optarg) == ParseBoolResult::kTrue) { + listTestsOnly = true; + } + break; + + case LongOpts::BenchmarkFilter: + if (!optarg) { + error = true; + break; + } + addTestsThatMatchFilter(optarg); + testsAreFiltered = true; + break; + case LongOpts::Renderer: if (!optarg) { error = true; @@ -346,11 +395,18 @@ void parseOptions(int argc, char* argv[]) { } } } while (optind < argc); - } else { + } else if (gRunTests.empty() && !testsAreFiltered) { for (auto& iter : TestScene::testMap()) { gRunTests.push_back(iter.second); } } + + if (listTestsOnly) { + for (auto& iter : gRunTests) { + std::cout << iter.name << std::endl; + } + exit(EXIT_SUCCESS); + } } int main(int argc, char* argv[]) { diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp index 138b3efd10ed..b8b3f0aa5229 100644 --- a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp +++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp @@ -46,7 +46,7 @@ RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_invalid) { EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease)); - // SkImage::MakeFromTexture should fail if given null GrDirectContext. + // SkImages::BorrowTextureFrom should fail if given null GrDirectContext. textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr); EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease)); diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp index 2b90bda87ecd..89d00d3c0dcb 100644 --- a/libs/hwui/tests/unit/CacheManagerTests.cpp +++ b/libs/hwui/tests/unit/CacheManagerTests.cpp @@ -20,7 +20,8 @@ #include "renderthread/EglManager.h" #include "tests/common/TestUtils.h" -#include <SkImagePriv.h> +#include <SkImageAndroid.h> +#include <include/gpu/ganesh/SkSurfaceGanesh.h> #include "include/gpu/GpuTypes.h" // from Skia using namespace android; @@ -34,7 +35,7 @@ static size_t getCacheUsage(GrDirectContext* grContext) { } // TOOD(258700630): fix this test and re-enable -RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) { +RENDERTHREAD_TEST(CacheManager, DISABLED_trimMemory) { int32_t width = DeviceInfo::get()->getWidth(); int32_t height = DeviceInfo::get()->getHeight(); GrDirectContext* grContext = renderThread.getGrContext(); @@ -46,8 +47,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, skgpu::Budgeted::kYes, - info); + sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(grContext, skgpu::Budgeted::kYes, + info); surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT); grContext->flushAndSubmit(); @@ -57,9 +58,8 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) { // create an image and pin it so that we have something with a unique key in the cache sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::MakeA8(width, height)); - sk_sp<SkImage> image = bitmap->makeImage(); - ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext)); - + sk_sp<SkImage> image = bitmap->makeImage(); // calls skgpu::ganesh::PinAsTexture under the hood. + ASSERT_TRUE(skgpu::ganesh::PinAsTexture(grContext, image.get())); // attempt to trim all memory while we still hold strong refs renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE); ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes()); @@ -71,7 +71,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) { } // unpin the image which should add a unique purgeable key to the cache - SkImage_unpinAsTexture(image.get(), grContext); + skgpu::ganesh::UnpinTexture(grContext, image.get()); // verify that we have enough purgeable bytes const size_t purgeableBytes = grContext->getResourceCachePurgeableBytes(); diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp index 9e376e32f8ea..47a41057cac9 100644 --- a/libs/hwui/tests/unit/CanvasContextTests.cpp +++ b/libs/hwui/tests/unit/CanvasContextTests.cpp @@ -19,6 +19,7 @@ #include "AnimationContext.h" #include "IContextFactory.h" #include "renderthread/CanvasContext.h" +#include "renderthread/VulkanManager.h" #include "tests/common/TestUtils.h" using namespace android; @@ -42,3 +43,38 @@ RENDERTHREAD_TEST(CanvasContext, create) { canvasContext->destroy(); } + +RENDERTHREAD_TEST(CanvasContext, buildLayerDoesntLeak) { + auto node = TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) { + canvas.drawColor(0xFFFF0000, SkBlendMode::kSrc); + }); + ASSERT_TRUE(node->isValid()); + EXPECT_EQ(LayerType::None, node->stagingProperties().effectiveLayerType()); + node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer); + + auto& cacheManager = renderThread.cacheManager(); + EXPECT_TRUE(cacheManager.areAllContextsStopped()); + ContextFactory contextFactory; + std::unique_ptr<CanvasContext> canvasContext( + CanvasContext::create(renderThread, false, node.get(), &contextFactory, 0, 0)); + canvasContext->buildLayer(node.get()); + EXPECT_TRUE(node->hasLayer()); + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { + auto instance = VulkanManager::peekInstance(); + if (instance) { + EXPECT_TRUE(instance->hasVkContext()); + } else { + ADD_FAILURE() << "VulkanManager wasn't initialized to buildLayer?"; + } + } + renderThread.destroyRenderingContext(); + EXPECT_FALSE(node->hasLayer()) << "Node still has a layer after rendering context destroyed"; + + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { + auto instance = VulkanManager::peekInstance(); + if (instance) { + ADD_FAILURE() << "VulkanManager still exists"; + EXPECT_FALSE(instance->hasVkContext()); + } + } +} diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp index 0c389bfe8b71..cfa18ae01b4f 100644 --- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp +++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp @@ -40,7 +40,7 @@ RENDERTHREAD_TEST(DeferredLayerUpdater, updateLayer) { // push the deferred updates to the layer SkBitmap bitmap; bitmap.allocN32Pixels(16, 16); - sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap); + sk_sp<SkImage> layerImage = SkImages::RasterFromBitmap(bitmap); layerUpdater->updateLayer(true, layerImage, 0, SkRect::MakeEmpty()); // the backing layer should now have all the properties applied. diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp new file mode 100644 index 000000000000..623be1e3f3f3 --- /dev/null +++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp @@ -0,0 +1,151 @@ +/* + * 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. + */ + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <private/performance_hint_private.h> +#include <renderthread/HintSessionWrapper.h> +#include <utils/Log.h> + +#include <chrono> + +#include "Properties.h" + +using namespace testing; +using namespace std::chrono_literals; + +APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123); +APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456); +int uiThreadId = 1; +int renderThreadId = 2; + +namespace android::uirenderer::renderthread { + +class HintSessionWrapperTests : public testing::Test { +public: + void SetUp() override; + void TearDown() override; + +protected: + std::shared_ptr<HintSessionWrapper> mWrapper; + + class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding { + public: + void init() override; + + MOCK_METHOD(APerformanceHintManager*, fakeGetManager, ()); + MOCK_METHOD(APerformanceHintSession*, fakeCreateSession, + (APerformanceHintManager*, const int32_t*, size_t, int64_t)); + MOCK_METHOD(void, fakeCloseSession, (APerformanceHintSession*)); + MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t)); + MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t)); + MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t)); + }; + + // Must be static so it can have function pointers we can point to with static methods + static std::shared_ptr<MockHintSessionBinding> sMockBinding; + + // Must be static so we can point to them as normal fn pointers with HintSessionBinding + static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); }; + static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager, + const int32_t* ids, size_t idsSize, + int64_t initialTarget) { + return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget); + } + static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager, + const int32_t* ids, size_t idsSize, + int64_t initialTarget) { + std::this_thread::sleep_for(50ms); + return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget); + } + static void stubCloseSession(APerformanceHintSession* session) { + sMockBinding->fakeCloseSession(session); + }; + static void stubUpdateTargetWorkDuration(APerformanceHintSession* session, + int64_t workDuration) { + sMockBinding->fakeUpdateTargetWorkDuration(session, workDuration); + } + static void stubReportActualWorkDuration(APerformanceHintSession* session, + int64_t workDuration) { + sMockBinding->fakeReportActualWorkDuration(session, workDuration); + } + static void stubSendHint(APerformanceHintSession* session, int32_t hintId) { + sMockBinding->fakeSendHint(session, hintId); + }; + void waitForWrapperReady() { mWrapper->mHintSessionFuture.wait(); } +}; + +std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding> + HintSessionWrapperTests::sMockBinding; + +void HintSessionWrapperTests::SetUp() { + // Pretend it's supported even if we're in an emulator + Properties::useHintManager = true; + sMockBinding = std::make_shared<NiceMock<MockHintSessionBinding>>(); + mWrapper = std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId); + mWrapper->mBinding = sMockBinding; + EXPECT_CALL(*sMockBinding, fakeGetManager).WillOnce(Return(managerPtr)); + ON_CALL(*sMockBinding, fakeCreateSession).WillByDefault(Return(sessionPtr)); +} + +void HintSessionWrapperTests::MockHintSessionBinding::init() { + sMockBinding->getManager = &stubGetManager; + if (sMockBinding->createSession == nullptr) { + sMockBinding->createSession = &stubCreateSession; + } + sMockBinding->closeSession = &stubCloseSession; + sMockBinding->updateTargetWorkDuration = &stubUpdateTargetWorkDuration; + sMockBinding->reportActualWorkDuration = &stubReportActualWorkDuration; + sMockBinding->sendHint = &stubSendHint; +} + +void HintSessionWrapperTests::TearDown() { + mWrapper = nullptr; + sMockBinding = nullptr; +} + +TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) { + EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); + sMockBinding->createSession = stubSlowCreateSession; + mWrapper->init(); + mWrapper = nullptr; +} + +TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) { + EXPECT_CALL(*sMockBinding, fakeCreateSession(managerPtr, _, Gt(1), _)).Times(1); + mWrapper->init(); + waitForWrapperReady(); +} + +TEST_F(HintSessionWrapperTests, loadUpHintsSendCorrectly) { + EXPECT_CALL(*sMockBinding, + fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP))) + .Times(1); + mWrapper->init(); + waitForWrapperReady(); + mWrapper->sendLoadIncreaseHint(); +} + +TEST_F(HintSessionWrapperTests, loadResetHintsSendCorrectly) { + EXPECT_CALL(*sMockBinding, + fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET))) + .Times(1); + mWrapper->init(); + waitForWrapperReady(); + mWrapper->sendLoadResetHint(); +} + +} // namespace android::uirenderer::renderthread
\ No newline at end of file diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index 596bd37e4cf5..073a8357e574 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -14,20 +14,22 @@ * limitations under the License. */ -#include <VectorDrawable.h> -#include <gtest/gtest.h> - #include <SkBlendMode.h> #include <SkClipStack.h> #include <SkSurface_Base.h> +#include <VectorDrawable.h> +#include <gtest/gtest.h> +#include <include/effects/SkImageFilters.h> #include <string.h> + #include "AnimationContext.h" #include "DamageAccumulator.h" #include "FatalTestCanvas.h" #include "IContextFactory.h" -#include "hwui/Paint.h" #include "RecordingCanvas.h" #include "SkiaCanvas.h" +#include "hwui/Paint.h" +#include "pipeline/skia/BackdropFilterDrawable.h" #include "pipeline/skia/SkiaDisplayList.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" #include "pipeline/skia/SkiaPipeline.h" @@ -141,7 +143,7 @@ TEST(RenderNodeDrawable, zReorder) { } TEST(RenderNodeDrawable, composeOnLayer) { - auto surface = SkSurface::MakeRasterN32Premul(1, 1); + auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1)); SkCanvas& canvas = *surface->getCanvas(); canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); @@ -152,7 +154,7 @@ TEST(RenderNodeDrawable, composeOnLayer) { }); // attach a layer to the render node - auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1); + auto surfaceLayer = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1)); auto canvas2 = surfaceLayer->getCanvas(); canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); rootNode->setLayerSurface(surfaceLayer); @@ -187,7 +189,7 @@ static SkMatrix getRecorderMatrix(const SkiaRecordingCanvas& recorder) { } TEST(RenderNodeDrawable, saveLayerClipAndMatrixRestore) { - auto surface = SkSurface::MakeRasterN32Premul(400, 800); + auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(400, 800)); SkCanvas& canvas = *surface->getCanvas(); canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); @@ -353,7 +355,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) { EXPECT_EQ(3, canvas.getIndex()); } -RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { +RENDERTHREAD_TEST(RenderNodeDrawable, emptyReceiver) { class ProjectionTestCanvas : public SkCanvas { public: ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {} @@ -417,7 +419,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { EXPECT_EQ(2, canvas.getDrawCounter()); } -RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) { +RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) { /* R is backward projected on B and C is a layer. A / \ @@ -1050,7 +1052,7 @@ TEST(RenderNodeDrawable, renderNode) { } // Verify that layers are composed with linear filtering. -RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) { +RENDERTHREAD_TEST(RenderNodeDrawable, layerComposeQuality) { static const int CANVAS_WIDTH = 1; static const int CANVAS_HEIGHT = 1; static const int LAYER_WIDTH = 1; @@ -1074,7 +1076,8 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) { }); layerNode->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer); - layerNode->setLayerSurface(SkSurface::MakeRasterN32Premul(LAYER_WIDTH, LAYER_HEIGHT)); + layerNode->setLayerSurface(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(LAYER_WIDTH, + LAYER_HEIGHT))); FrameTestCanvas canvas; RenderNodeDrawable drawable(layerNode.get(), &canvas, true); @@ -1167,7 +1170,7 @@ TEST(ReorderBarrierDrawable, testShadowMatrix) { } // Draw a vector drawable twice but with different bounds and verify correct bounds are used. -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) { +RENDERTHREAD_TEST(SkiaRecordingCanvas, drawVectorDrawable) { static const int CANVAS_WIDTH = 100; static const int CANVAS_HEIGHT = 200; class VectorDrawableTestCanvas : public TestCanvasBase { @@ -1210,3 +1213,77 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) { canvas.drawDrawable(&drawable); EXPECT_EQ(2, canvas.mDrawCounter); } + +// Verify drawing logics for BackdropFilterDrawable +RENDERTHREAD_TEST(BackdropFilterDrawable, drawing) { + static const int CANVAS_WIDTH = 100; + static const int CANVAS_HEIGHT = 200; + class SimpleTestCanvas : public TestCanvasBase { + public: + SkRect mDstBounds; + SimpleTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {} + void onDrawRect(const SkRect& rect, const SkPaint& paint) override { + // did nothing. + } + + // called when BackdropFilterDrawable is drawn. + void onDrawImageRect2(const SkImage*, const SkRect& src, const SkRect& dst, + const SkSamplingOptions&, const SkPaint*, + SrcRectConstraint) override { + mDrawCounter++; + mDstBounds = dst; + } + }; + class SimpleLayer : public SkSurface_Base { + public: + SimpleLayer() + : SkSurface_Base(SkImageInfo::MakeN32Premul(CANVAS_WIDTH, CANVAS_HEIGHT), nullptr) { + } + virtual sk_sp<SkImage> onNewImageSnapshot(const SkIRect* bounds) override { + SkBitmap bitmap; + bitmap.allocN32Pixels(CANVAS_WIDTH, CANVAS_HEIGHT); + bitmap.setImmutable(); + return bitmap.asImage(); + } + SkCanvas* onNewCanvas() override { return new SimpleTestCanvas(); } + sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; } + bool onCopyOnWrite(ContentChangeMode) override { return true; } + void onWritePixels(const SkPixmap&, int x, int y) {} + }; + + auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, + [](RenderProperties& props, SkiaRecordingCanvas& canvas) { + canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, + Paint()); + }); + + sk_sp<SkSurface> surface(new SimpleLayer()); + auto* canvas = reinterpret_cast<SimpleTestCanvas*>(surface->getCanvas()); + RenderNodeDrawable drawable(node.get(), canvas, true); + BackdropFilterDrawable backdropDrawable(node.get(), canvas); + canvas->drawDrawable(&drawable); + canvas->drawDrawable(&backdropDrawable); + // no backdrop filter, skip drawing. + EXPECT_EQ(0, canvas->mDrawCounter); + + sk_sp<SkImageFilter> filter(SkImageFilters::Blur(3, 3, nullptr)); + node->animatorProperties().mutateLayerProperties().setBackdropImageFilter(filter.get()); + canvas->drawDrawable(&drawable); + canvas->drawDrawable(&backdropDrawable); + // backdrop filter is set, ok to draw. + EXPECT_EQ(1, canvas->mDrawCounter); + EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), canvas->mDstBounds); + + canvas->translate(30, 30); + canvas->drawDrawable(&drawable); + canvas->drawDrawable(&backdropDrawable); + // the drawable is still visible, ok to draw. + EXPECT_EQ(2, canvas->mDrawCounter); + EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH - 30, CANVAS_HEIGHT - 30), canvas->mDstBounds); + + canvas->translate(CANVAS_WIDTH, CANVAS_HEIGHT); + canvas->drawDrawable(&drawable); + canvas->drawDrawable(&backdropDrawable); + // the drawable is invisible, skip drawing. + EXPECT_EQ(2, canvas->mDrawCounter); +} diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index 80796f4a4111..8273524167f9 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -231,8 +231,7 @@ TEST(RenderNode, multiTreeValidity) { } TEST(RenderNode, releasedCallback) { - int functor = WebViewFunctor_create( - nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + int functor = TestUtils::createMockFunctor(); auto node = TestUtils::createNode(0, 0, 200, 400, [&](RenderProperties& props, Canvas& canvas) { canvas.drawWebViewFunctor(functor); diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 7bcd45c6b643..0f8bd1368f5a 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -49,7 +49,7 @@ public: */ static void reinitializeAllFields(ShaderCache& cache) { ShaderCache newCache = ShaderCache(); - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex), newLock(newCache.mMutex); // By order of declaration cache.mInitialized = newCache.mInitialized; cache.mBlobCache.reset(nullptr); @@ -72,7 +72,7 @@ public: * manually, as seen in the "terminate" testing helper function. */ static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) { - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); cache.mDeferredSaveDelayMs = saveDelayMs; } @@ -81,7 +81,7 @@ public: * Next call to "initShaderDiskCache" will load again the in-memory cache from disk. */ static void terminate(ShaderCache& cache, bool saveContent) { - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); if (saveContent) { cache.saveToDiskLocked(); } @@ -93,6 +93,7 @@ public: */ template <typename T> static bool validateCache(ShaderCache& cache, std::vector<T> hash) { + std::lock_guard lock(cache.mMutex); return cache.validateCache(hash.data(), hash.size() * sizeof(T)); } @@ -108,7 +109,7 @@ public: */ static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) { { - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); ASSERT_TRUE(cache.mSavePending); } bool saving = true; @@ -123,7 +124,7 @@ public: usleep(delayMicroseconds); elapsedMilliseconds += (float)delayMicroseconds / 1000; - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); saving = cache.mSavePending; } } @@ -369,9 +370,9 @@ TEST(ShaderCacheTest, testCacheValidation) { } using namespace android::uirenderer; -RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) { +RENDERTHREAD_TEST(ShaderCacheTest, testOnVkFrameFlushed) { if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) { - // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants. + // RENDERTHREAD_TEST declares both SkiaVK and SkiaGL variants. GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan"; } if (!folderExist(getExternalStorageFolder())) { diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp index 87c52161d68e..e53fcaa474e2 100644 --- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp +++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp @@ -36,7 +36,7 @@ using namespace android; using namespace android::uirenderer; TEST(SkiaCanvas, drawShadowLayer) { - auto surface = SkSurface::MakeRasterN32Premul(10, 10); + auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10)); SkiaCanvas canvas(surface->getCanvas()); // clear to white diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp index f825d7c5d9cc..064d42ec8941 100644 --- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp +++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp @@ -48,8 +48,7 @@ TEST(SkiaDisplayList, reset) { SkCanvas dummyCanvas; RenderNodeDrawable drawable(nullptr, &dummyCanvas); skiaDL->mChildNodes.emplace_back(nullptr, &dummyCanvas); - int functor1 = WebViewFunctor_create( - nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + int functor1 = TestUtils::createMockFunctor(); GLFunctorDrawable functorDrawable{functor1, &dummyCanvas}; WebViewFunctor_release(functor1); skiaDL->mChildFunctors.push_back(&functorDrawable); @@ -101,8 +100,7 @@ TEST(SkiaDisplayList, syncContexts) { SkCanvas dummyCanvas; - int functor1 = WebViewFunctor_create( - nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + int functor1 = TestUtils::createMockFunctor(); auto& counts = TestUtils::countsForFunctor(functor1); skiaDL.mChildFunctors.push_back( skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas)); @@ -131,6 +129,33 @@ TEST(SkiaDisplayList, syncContexts) { EXPECT_EQ(counts.destroyed, 1); } +TEST(SkiaDisplayList, recordMutableBitmap) { + SkiaRecordingCanvas canvas{nullptr, 100, 100}; + auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make( + 10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType)); + EXPECT_FALSE(bitmap->isImmutable()); + canvas.drawBitmap(*bitmap, 0, 0, nullptr); + auto displayList = canvas.finishRecording(); + ASSERT_EQ(1, displayList->mMutableImages.size()); + EXPECT_EQ(10, displayList->mMutableImages[0]->width()); + EXPECT_EQ(20, displayList->mMutableImages[0]->height()); +} + +TEST(SkiaDisplayList, recordMutableBitmapInShader) { + SkiaRecordingCanvas canvas{nullptr, 100, 100}; + auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make( + 10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType)); + EXPECT_FALSE(bitmap->isImmutable()); + SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone); + Paint paint; + paint.setShader(bitmap->makeImage()->makeShader(sampling)); + canvas.drawPaint(paint); + auto displayList = canvas.finishRecording(); + ASSERT_EQ(1, displayList->mMutableImages.size()); + EXPECT_EQ(10, displayList->mMutableImages[0]->width()); + EXPECT_EQ(20, displayList->mMutableImages[0]->height()); +} + class ContextFactory : public IContextFactory { public: virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { @@ -138,7 +163,7 @@ public: } }; -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) { +RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren) { auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( @@ -197,7 +222,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) { canvasContext->destroy(); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) { +RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) { auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 4d0595e03da6..3ded540c3152 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -42,7 +42,7 @@ using namespace android::uirenderer; using namespace android::uirenderer::renderthread; using namespace android::uirenderer::skiapipeline; -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) { +RENDERTHREAD_TEST(SkiaPipeline, renderFrame) { auto redNode = TestUtils::createSkiaNode( 0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); @@ -54,7 +54,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) { bool opaque = true; android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); - auto surface = SkSurface::MakeRasterN32Premul(1, 1); + auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1)); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, @@ -62,7 +62,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) { +RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) { auto halfGreenNode = TestUtils::createSkiaNode( 0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) { Paint greenPaint; @@ -76,7 +76,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) { renderNodes.push_back(halfGreenNode); android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); - auto surface = SkSurface::MakeRasterN32Premul(2, 2); + auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2)); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface, @@ -89,7 +89,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) { ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { +RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { auto redNode = TestUtils::createSkiaNode( 0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); @@ -100,7 +100,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { renderNodes.push_back(redNode); android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); - auto surface = SkSurface::MakeRasterN32Premul(2, 2); + auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2)); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface, @@ -111,12 +111,12 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) { +RENDERTHREAD_TEST(SkiaPipeline, renderLayer) { auto redNode = TestUtils::createSkiaNode( 0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); }); - auto surfaceLayer1 = SkSurface::MakeRasterN32Premul(1, 1); + auto surfaceLayer1 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1)); surfaceLayer1->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorWHITE); redNode->setLayerSurface(surfaceLayer1); @@ -127,7 +127,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) { 0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) { blueCanvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); }); - auto surfaceLayer2 = SkSurface::MakeRasterN32Premul(2, 2); + auto surfaceLayer2 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2)); surfaceLayer2->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorWHITE); blueNode->setLayerSurface(surfaceLayer2); @@ -154,7 +154,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) { blueNode->setLayerSurface(sk_sp<SkSurface>()); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) { +RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) { ScopedProperty<bool> prop(Properties::debugOverdraw, true); auto whiteNode = TestUtils::createSkiaNode( @@ -169,7 +169,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) { // empty contentDrawBounds is avoiding backdrop/content logic, which would lead to less overdraw android::uirenderer::Rect contentDrawBounds(0, 0, 0, 0); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); - auto surface = SkSurface::MakeRasterN32Premul(1, 1); + auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1)); // Initialize the canvas to blue. surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); @@ -227,7 +227,7 @@ public: }; } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) { +RENDERTHREAD_TEST(SkiaPipeline, deferRenderNodeScene) { class DeferTestCanvas : public SkCanvas { public: DeferTestCanvas() : SkCanvas(800, 600) {} @@ -297,7 +297,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) { EXPECT_EQ(4, surface->canvas()->mDrawCounter); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) { +RENDERTHREAD_TEST(SkiaPipeline, clipped) { static const int CANVAS_WIDTH = 200; static const int CANVAS_HEIGHT = 200; class ClippedTestCanvas : public SkCanvas { @@ -330,7 +330,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) { } // Test renderFrame with a dirty clip and a pre-transform matrix. -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) { +RENDERTHREAD_TEST(SkiaPipeline, clipped_rotated) { static const int CANVAS_WIDTH = 200; static const int CANVAS_HEIGHT = 100; static const SkMatrix rotateMatrix = SkMatrix::MakeAll(0, -1, CANVAS_HEIGHT, 1, 0, 0, 0, 0, 1); @@ -366,7 +366,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) { EXPECT_EQ(1, surface->canvas()->mDrawCounter); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) { +RENDERTHREAD_TEST(SkiaPipeline, clip_replace) { static const int CANVAS_WIDTH = 50; static const int CANVAS_HEIGHT = 50; class ClipReplaceTestCanvas : public SkCanvas { @@ -396,7 +396,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) { EXPECT_EQ(1, surface->canvas()->mDrawCounter); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { +RENDERTHREAD_TEST(SkiaPipeline, context_lost) { test::TestContext context; auto surface = context.surface(); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); @@ -410,7 +410,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { EXPECT_TRUE(pipeline->isSurfaceReady()); } -RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) { +RENDERTHREAD_TEST(SkiaPipeline, pictureCallback) { // create a pipeline and add a picture callback auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); int callbackCount = 0; @@ -428,7 +428,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) { renderNodes.push_back(redNode); bool opaque = true; android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1); - auto surface = SkSurface::MakeRasterN32Premul(1, 1); + auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1)); pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, SkMatrix::I()); diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp index e1fb8b7069ff..5e8f13d261c7 100644 --- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp +++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp @@ -26,9 +26,15 @@ using namespace android; using namespace android::uirenderer; +#define ASSUME_GLES() \ + if (WebViewFunctor_queryPlatformRenderMode() != RenderMode::OpenGL_ES) \ + GTEST_SKIP() << "Not in GLES, skipping test" + TEST(WebViewFunctor, createDestroyGLES) { + ASSUME_GLES(); int functor = WebViewFunctor_create( - nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES), + RenderMode::OpenGL_ES); ASSERT_NE(-1, functor); WebViewFunctor_release(functor); TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { @@ -41,8 +47,10 @@ TEST(WebViewFunctor, createDestroyGLES) { } TEST(WebViewFunctor, createSyncHandleGLES) { + ASSUME_GLES(); int functor = WebViewFunctor_create( - nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES), + RenderMode::OpenGL_ES); ASSERT_NE(-1, functor); auto handle = WebViewFunctorManager::instance().handleFor(functor); ASSERT_TRUE(handle); @@ -82,8 +90,10 @@ TEST(WebViewFunctor, createSyncHandleGLES) { } TEST(WebViewFunctor, createSyncDrawGLES) { + ASSUME_GLES(); int functor = WebViewFunctor_create( - nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES), + RenderMode::OpenGL_ES); ASSERT_NE(-1, functor); auto handle = WebViewFunctorManager::instance().handleFor(functor); ASSERT_TRUE(handle); @@ -108,9 +118,11 @@ TEST(WebViewFunctor, createSyncDrawGLES) { EXPECT_EQ(1, counts.destroyed); } -TEST(WebViewFunctor, contextDestroyed) { +TEST(WebViewFunctor, contextDestroyedGLES) { + ASSUME_GLES(); int functor = WebViewFunctor_create( - nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES), + RenderMode::OpenGL_ES); ASSERT_NE(-1, functor); auto handle = WebViewFunctorManager::instance().handleFor(functor); ASSERT_TRUE(handle); diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp index 10c874ec3f12..76cbc8abc808 100644 --- a/libs/hwui/tests/unit/main.cpp +++ b/libs/hwui/tests/unit/main.cpp @@ -14,15 +14,15 @@ * limitations under the License. */ -#include "gmock/gmock.h" -#include "gtest/gtest.h" +#include <getopt.h> +#include <signal.h> #include "Properties.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" #include "hwui/Typeface.h" #include "tests/common/LeakChecker.h" -#include <signal.h> - using namespace std; using namespace android; using namespace android::uirenderer; @@ -45,6 +45,57 @@ static void gtestSigHandler(int sig, siginfo_t* siginfo, void* context) { raise(sig); } +// For options that only exist in long-form. Anything in the +// 0-255 range is reserved for short options (which just use their ASCII value) +namespace LongOpts { +enum { + Reserved = 255, + Renderer, +}; +} + +static const struct option LONG_OPTIONS[] = { + {"renderer", required_argument, nullptr, LongOpts::Renderer}, {0, 0, 0, 0}}; + +static RenderPipelineType parseRenderer(const char* renderer) { + // Anything that's not skiavk is skiagl + if (!strcmp(renderer, "skiavk")) { + return RenderPipelineType::SkiaVulkan; + } + return RenderPipelineType::SkiaGL; +} + +struct Options { + RenderPipelineType renderer = RenderPipelineType::SkiaGL; +}; + +Options parseOptions(int argc, char* argv[]) { + int c; + opterr = 0; + Options opts; + + while (true) { + /* getopt_long stores the option index here. */ + int option_index = 0; + + c = getopt_long(argc, argv, "", LONG_OPTIONS, &option_index); + + if (c == -1) break; + + switch (c) { + case 0: + // Option set a flag, don't need to do anything + // (although none of the current LONG_OPTIONS do this...) + break; + + case LongOpts::Renderer: + opts.renderer = parseRenderer(optarg); + break; + } + } + return opts; +} + class TypefaceEnvironment : public testing::Environment { public: virtual void SetUp() { Typeface::setRobotoTypefaceForTest(); } @@ -64,8 +115,9 @@ int main(int argc, char* argv[]) { // Avoid talking to SF Properties::isolatedProcess = true; - // Default to GLES (Vulkan-aware tests will override this) - Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL); + + auto opts = parseOptions(argc, argv); + Properties::overrideRenderPipelineType(opts.renderer); // Run the tests testing::InitGoogleTest(&argc, argv); diff --git a/libs/hwui/utils/LinearAllocator.cpp b/libs/hwui/utils/LinearAllocator.cpp index 8baa4b770f85..eab888ef4d1a 100644 --- a/libs/hwui/utils/LinearAllocator.cpp +++ b/libs/hwui/utils/LinearAllocator.cpp @@ -31,15 +31,11 @@ #include <utils/Log.h> #include <utils/Macros.h> -// The ideal size of a page allocation (these need to be multiples of 8) -#define INITIAL_PAGE_SIZE ((size_t)512) // 512b -#define MAX_PAGE_SIZE ((size_t)131072) // 128kb - // The maximum amount of wasted space we can have per page // Allocations exceeding this will have their own dedicated page // If this is too low, we will malloc too much // Too high, and we may waste too much space -// Must be smaller than INITIAL_PAGE_SIZE +// Must be smaller than kInitialPageSize #define MAX_WASTE_RATIO (0.5f) #if LOG_NDEBUG @@ -75,6 +71,10 @@ static void _addAllocation(int count) { namespace android { namespace uirenderer { +// The ideal size of a page allocation (these need to be multiples of 8) +static constexpr size_t kInitialPageSize = 512; // 512b +static constexpr size_t kMaxPageSize = 131072; // 128kb + class LinearAllocator::Page { public: Page* next() { return mNextPage; } @@ -94,8 +94,8 @@ private: }; LinearAllocator::LinearAllocator() - : mPageSize(INITIAL_PAGE_SIZE) - , mMaxAllocSize(INITIAL_PAGE_SIZE * MAX_WASTE_RATIO) + : mPageSize(kInitialPageSize) + , mMaxAllocSize(kInitialPageSize * MAX_WASTE_RATIO) , mNext(0) , mCurrentPage(0) , mPages(0) @@ -135,8 +135,8 @@ bool LinearAllocator::fitsInCurrentPage(size_t size) { void LinearAllocator::ensureNext(size_t size) { if (fitsInCurrentPage(size)) return; - if (mCurrentPage && mPageSize < MAX_PAGE_SIZE) { - mPageSize = min(MAX_PAGE_SIZE, mPageSize * 2); + if (mCurrentPage && mPageSize < kMaxPageSize) { + mPageSize = min(kMaxPageSize, mPageSize * 2); mMaxAllocSize = mPageSize * MAX_WASTE_RATIO; mPageSize = ALIGN(mPageSize); } diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp index db495cfbf7e1..858813ff4276 100644 --- a/libs/incident/src/IncidentReportArgs.cpp +++ b/libs/incident/src/IncidentReportArgs.cpp @@ -133,13 +133,12 @@ IncidentReportArgs::readFromParcel(const Parcel* in) mSections.insert(section); } - int32_t headerCount; - err = in->readInt32(&headerCount); + err = in->resizeOutVector<vector<uint8_t>>(&mHeaders); if (err != NO_ERROR) { return err; } - mHeaders.resize(headerCount); - for (int i=0; i<headerCount; i++) { + + for (int i=0; i<mHeaders.size(); i++) { err = in->readByteVector(&mHeaders[i]); if (err != NO_ERROR) { return err; @@ -153,8 +152,8 @@ IncidentReportArgs::readFromParcel(const Parcel* in) } mPrivacyPolicy = privacyPolicy; - mReceiverPkg = String8(in->readString16()).string(); - mReceiverCls = String8(in->readString16()).string(); + mReceiverPkg = String8(in->readString16()).c_str(); + mReceiverCls = String8(in->readString16()).c_str(); int32_t gzip; err = in->readInt32(&gzip); diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 6c0fd5f65359..5ce990fdeb82 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -23,6 +23,12 @@ package { cc_library_shared { name: "libinputservice", + defaults: [ + // Build using the same flags and configurations as inputflinger. + "inputflinger_defaults", + ], + host_supported: false, + srcs: [ "PointerController.cpp", "PointerControllerContext.cpp", @@ -50,12 +56,4 @@ cc_library_shared { ], include_dirs: ["frameworks/native/services"], - - cflags: [ - "-Wall", - "-Wextra", - "-Werror", - "-Wthread-safety", - ], - } diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index c3ad7670d473..6a465442c2b4 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -47,7 +47,7 @@ MouseCursorController::MouseCursorController(PointerControllerContext& context) mLocked.pointerX = 0; mLocked.pointerY = 0; mLocked.pointerAlpha = 0.0f; // pointer is initially faded - mLocked.pointerSprite = mContext.getSpriteController()->createSprite(); + mLocked.pointerSprite = mContext.getSpriteController().createSprite(); mLocked.updatePointerIcon = false; mLocked.requestedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED; mLocked.resolvedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED; @@ -325,8 +325,8 @@ bool MouseCursorController::doBitmapAnimationLocked(nsecs_t timestamp) REQUIRES( } if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) { - sp<SpriteController> spriteController = mContext.getSpriteController(); - spriteController->openTransaction(); + auto& spriteController = mContext.getSpriteController(); + spriteController.openTransaction(); int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame; mLocked.animationFrameIndex += incr; @@ -336,7 +336,7 @@ bool MouseCursorController::doBitmapAnimationLocked(nsecs_t timestamp) REQUIRES( } mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]); - spriteController->closeTransaction(); + spriteController.closeTransaction(); } // Keep animating. return true; @@ -346,8 +346,8 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) { if (!mLocked.viewport.isValid()) { return; } - sp<SpriteController> spriteController = mContext.getSpriteController(); - spriteController->openTransaction(); + auto& spriteController = mContext.getSpriteController(); + spriteController.openTransaction(); mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); @@ -392,7 +392,7 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) { mLocked.updatePointerIcon = false; } - spriteController->closeTransaction(); + spriteController.closeTransaction(); } void MouseCursorController::loadResourcesLocked(bool getAdditionalMouseResources) REQUIRES(mLock) { diff --git a/libs/input/OWNERS b/libs/input/OWNERS index d701f23cb9b8..4c20c4dc9d35 100644 --- a/libs/input/OWNERS +++ b/libs/input/OWNERS @@ -1 +1 @@ -include /core/java/android/hardware/input/OWNERS +include /INPUT_OWNERS diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index e21d6fb2fe14..abd928486607 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -63,10 +63,10 @@ void PointerController::DisplayInfoListener::onPointerControllerDestroyed() { std::shared_ptr<PointerController> PointerController::create( const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - const sp<SpriteController>& spriteController) { + SpriteController& spriteController, bool enabled) { // using 'new' to access non-public constructor std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>( - new PointerController(policy, looper, spriteController)); + new PointerController(policy, looper, spriteController, enabled)); /* * Now we need to hook up the constructed PointerController object to its callbacks. @@ -85,10 +85,10 @@ std::shared_ptr<PointerController> PointerController::create( } PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, - const sp<Looper>& looper, - const sp<SpriteController>& spriteController) + const sp<Looper>& looper, SpriteController& spriteController, + bool enabled) : PointerController( - policy, looper, spriteController, + policy, looper, spriteController, enabled, [](const sp<android::gui::WindowInfosListener>& listener) { SurfaceComposerClient::getDefault()->addWindowInfosListener(listener); }, @@ -97,13 +97,13 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& }) {} PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, - const sp<Looper>& looper, - const sp<SpriteController>& spriteController, - WindowListenerConsumer registerListener, + const sp<Looper>& looper, SpriteController& spriteController, + bool enabled, WindowListenerConsumer registerListener, WindowListenerConsumer unregisterListener) - : mContext(policy, looper, spriteController, *this), + : mEnabled(enabled), + mContext(policy, looper, spriteController, *this), mCursorController(mContext), - mDisplayInfoListener(new DisplayInfoListener(this)), + mDisplayInfoListener(sp<DisplayInfoListener>::make(this)), mUnregisterWindowInfosListener(std::move(unregisterListener)) { std::scoped_lock lock(getLock()); mLocked.presentation = Presentation::SPOT; @@ -121,10 +121,14 @@ std::mutex& PointerController::getLock() const { } std::optional<FloatRect> PointerController::getBounds() const { + if (!mEnabled) return {}; + return mCursorController.getBounds(); } void PointerController::move(float deltaX, float deltaY) { + if (!mEnabled) return; + const int32_t displayId = mCursorController.getDisplayId(); vec2 transformed; { @@ -136,6 +140,8 @@ void PointerController::move(float deltaX, float deltaY) { } void PointerController::setPosition(float x, float y) { + if (!mEnabled) return; + const int32_t displayId = mCursorController.getDisplayId(); vec2 transformed; { @@ -147,6 +153,11 @@ void PointerController::setPosition(float x, float y) { } FloatPoint PointerController::getPosition() const { + if (!mEnabled) { + return FloatPoint{AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION}; + } + const int32_t displayId = mCursorController.getDisplayId(); const auto p = mCursorController.getPosition(); { @@ -157,20 +168,28 @@ FloatPoint PointerController::getPosition() const { } int32_t PointerController::getDisplayId() const { + if (!mEnabled) return ADISPLAY_ID_NONE; + return mCursorController.getDisplayId(); } void PointerController::fade(Transition transition) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); mCursorController.fade(transition); } void PointerController::unfade(Transition transition) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); mCursorController.unfade(transition); } void PointerController::setPresentation(Presentation presentation) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); if (mLocked.presentation == presentation) { @@ -195,6 +214,8 @@ void PointerController::setPresentation(Presentation presentation) { void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; const ui::Transform& transform = getTransformForDisplayLocked(displayId); @@ -218,6 +239,8 @@ void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t } void PointerController::clearSpots() { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); clearSpotsLocked(); } @@ -279,11 +302,15 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) { } void PointerController::updatePointerIcon(PointerIconStyle iconId) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); mCursorController.updatePointerIcon(iconId); } void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { + if (!mEnabled) return; + std::scoped_lock lock(getLock()); mCursorController.setCustomPointerIcon(icon); } @@ -292,7 +319,7 @@ void PointerController::doInactivityTimeout() { fade(Transition::GRADUAL); } -void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) { +void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) { std::unordered_set<int32_t> displayIdSet; for (const DisplayViewport& viewport : viewports) { displayIdSet.insert(viewport.displayId); @@ -327,8 +354,12 @@ 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::string PointerController::dump() { + if (!mEnabled) { + return INDENT "PointerController: DISABLED due to ongoing PointerChoreographer refactor\n"; + } + + std::string dump = INDENT "PointerController:\n"; std::scoped_lock lock(getLock()); dump += StringPrintf(INDENT2 "Presentation: %s\n", ftl::enum_string(mLocked.presentation).c_str()); @@ -341,6 +372,7 @@ void PointerController::dump(std::string& dump) { for (const auto& [_, spotController] : mLocked.spotControllers) { spotController.dump(dump, INDENT3); } + return dump; } } // namespace android diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 62ee74331302..aa7ca3c52ecf 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -47,7 +47,7 @@ class PointerController : public PointerControllerInterface { public: static std::shared_ptr<PointerController> create( const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - const sp<SpriteController>& spriteController); + SpriteController& spriteController, bool enabled); ~PointerController() override; @@ -70,12 +70,12 @@ public: void setInactivityTimeout(InactivityTimeout inactivityTimeout); void doInactivityTimeout(); void reloadPointerResources(); - void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports); + void onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports); void onDisplayInfosChangedLocked(const std::vector<gui::DisplayInfo>& displayInfos) REQUIRES(getLock()); - void dump(std::string& dump); + std::string dump(); protected: using WindowListenerConsumer = @@ -83,13 +83,13 @@ protected: // Constructor used to test WindowInfosListener registration. PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - const sp<SpriteController>& spriteController, + SpriteController& spriteController, bool enabled, WindowListenerConsumer registerListener, WindowListenerConsumer unregisterListener); private: PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - const sp<SpriteController>& spriteController); + SpriteController& spriteController, bool enabled); friend PointerControllerContext::LooperCallback; friend PointerControllerContext::MessageHandler; @@ -100,6 +100,8 @@ private: // we use the DisplayInfoListener's lock in PointerController. std::mutex& getLock() const; + const bool mEnabled; + PointerControllerContext mContext; MouseCursorController mCursorController; diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp index f30e8d8e33a5..15c35176afce 100644 --- a/libs/input/PointerControllerContext.cpp +++ b/libs/input/PointerControllerContext.cpp @@ -32,12 +32,12 @@ namespace android { PointerControllerContext::PointerControllerContext( const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - const sp<SpriteController>& spriteController, PointerController& controller) + SpriteController& spriteController, PointerController& controller) : mPolicy(policy), mLooper(looper), mSpriteController(spriteController), - mHandler(new MessageHandler()), - mCallback(new LooperCallback()), + mHandler(sp<MessageHandler>::make()), + mCallback(sp<LooperCallback>::make()), mController(controller), mAnimator(*this) { std::scoped_lock lock(mLock); @@ -93,7 +93,7 @@ sp<PointerControllerPolicyInterface> PointerControllerContext::getPolicy() { return mPolicy; } -sp<SpriteController> PointerControllerContext::getSpriteController() { +SpriteController& PointerControllerContext::getSpriteController() { return mSpriteController; } diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h index f6f5d3bc51bd..98c3988e7df4 100644 --- a/libs/input/PointerControllerContext.h +++ b/libs/input/PointerControllerContext.h @@ -92,7 +92,7 @@ public: class PointerControllerContext { public: PointerControllerContext(const sp<PointerControllerPolicyInterface>& policy, - const sp<Looper>& looper, const sp<SpriteController>& spriteController, + const sp<Looper>& looper, SpriteController& spriteController, PointerController& controller); ~PointerControllerContext(); @@ -109,7 +109,7 @@ public: void setCallbackController(std::shared_ptr<PointerController> controller); sp<PointerControllerPolicyInterface> getPolicy(); - sp<SpriteController> getSpriteController(); + SpriteController& getSpriteController(); void handleDisplayEvents(); @@ -163,7 +163,7 @@ private: sp<PointerControllerPolicyInterface> mPolicy; sp<Looper> mLooper; - sp<SpriteController> mSpriteController; + SpriteController& mSpriteController; sp<MessageHandler> mHandler; sp<LooperCallback> mCallback; diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index 130b204954b4..6dc45a6aebec 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -31,12 +31,19 @@ SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLaye ParentSurfaceProvider parentSurfaceProvider) : mLooper(looper), mOverlayLayer(overlayLayer), + mHandler(sp<Handler>::make()), mParentSurfaceProvider(std::move(parentSurfaceProvider)) { - mHandler = new WeakMessageHandler(this); mLocked.transactionNestingCount = 0; mLocked.deferredSpriteUpdate = false; } +void SpriteController::setHandlerController( + const std::shared_ptr<android::SpriteController>& controller) { + // Initialize the weak message handler outside the constructor, because we cannot get a shared + // pointer to self in the constructor. + mHandler->spriteController = controller; +} + SpriteController::~SpriteController() { mLooper->removeMessages(mHandler); @@ -47,7 +54,7 @@ SpriteController::~SpriteController() { } sp<Sprite> SpriteController::createSprite() { - return new SpriteImpl(this); + return sp<SpriteImpl>::make(*this); } void SpriteController::openTransaction() { @@ -65,7 +72,7 @@ void SpriteController::closeTransaction() { mLocked.transactionNestingCount -= 1; if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) { mLocked.deferredSpriteUpdate = false; - mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES)); + mLooper->sendMessage(mHandler, Message(Handler::MSG_UPDATE_SPRITES)); } } @@ -76,7 +83,7 @@ void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) { if (mLocked.transactionNestingCount != 0) { mLocked.deferredSpriteUpdate = true; } else { - mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES)); + mLooper->sendMessage(mHandler, Message(Handler::MSG_UPDATE_SPRITES)); } } } @@ -85,18 +92,7 @@ void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceCon bool wasEmpty = mLocked.disposedSurfaces.empty(); mLocked.disposedSurfaces.push_back(surfaceControl); if (wasEmpty) { - mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES)); - } -} - -void SpriteController::handleMessage(const Message& message) { - switch (message.what) { - case MSG_UPDATE_SPRITES: - doUpdateSprites(); - break; - case MSG_DISPOSE_SURFACES: - doDisposeSurfaces(); - break; + mLooper->sendMessage(mHandler, Message(Handler::MSG_DISPOSE_SURFACES)); } } @@ -327,7 +323,7 @@ void SpriteController::doDisposeSurfaces() { void SpriteController::ensureSurfaceComposerClient() { if (mSurfaceComposerClient == NULL) { - mSurfaceComposerClient = new SurfaceComposerClient(); + mSurfaceComposerClient = sp<SurfaceComposerClient>::make(); } } @@ -353,25 +349,41 @@ sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height return surfaceControl; } -// --- SpriteController::SpriteImpl --- +// --- SpriteController::Handler --- -SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) : - mController(controller) { +void SpriteController::Handler::handleMessage(const android::Message& message) { + auto controller = spriteController.lock(); + if (!controller) { + return; + } + + switch (message.what) { + case MSG_UPDATE_SPRITES: + controller->doUpdateSprites(); + break; + case MSG_DISPOSE_SURFACES: + controller->doDisposeSurfaces(); + break; + } } +// --- SpriteController::SpriteImpl --- + +SpriteController::SpriteImpl::SpriteImpl(SpriteController& controller) : mController(controller) {} + SpriteController::SpriteImpl::~SpriteImpl() { - AutoMutex _m(mController->mLock); + AutoMutex _m(mController.mLock); // Let the controller take care of deleting the last reference to sprite // surfaces so that we do not block the caller on an IPC here. if (mLocked.state.surfaceControl != NULL) { - mController->disposeSurfaceLocked(mLocked.state.surfaceControl); + mController.disposeSurfaceLocked(mLocked.state.surfaceControl); mLocked.state.surfaceControl.clear(); } } void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) { - AutoMutex _l(mController->mLock); + AutoMutex _l(mController.mLock); uint32_t dirty; if (icon.isValid()) { @@ -401,7 +413,7 @@ void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) { } void SpriteController::SpriteImpl::setVisible(bool visible) { - AutoMutex _l(mController->mLock); + AutoMutex _l(mController.mLock); if (mLocked.state.visible != visible) { mLocked.state.visible = visible; @@ -410,7 +422,7 @@ void SpriteController::SpriteImpl::setVisible(bool visible) { } void SpriteController::SpriteImpl::setPosition(float x, float y) { - AutoMutex _l(mController->mLock); + AutoMutex _l(mController.mLock); if (mLocked.state.positionX != x || mLocked.state.positionY != y) { mLocked.state.positionX = x; @@ -420,7 +432,7 @@ void SpriteController::SpriteImpl::setPosition(float x, float y) { } void SpriteController::SpriteImpl::setLayer(int32_t layer) { - AutoMutex _l(mController->mLock); + AutoMutex _l(mController.mLock); if (mLocked.state.layer != layer) { mLocked.state.layer = layer; @@ -429,7 +441,7 @@ void SpriteController::SpriteImpl::setLayer(int32_t layer) { } void SpriteController::SpriteImpl::setAlpha(float alpha) { - AutoMutex _l(mController->mLock); + AutoMutex _l(mController.mLock); if (mLocked.state.alpha != alpha) { mLocked.state.alpha = alpha; @@ -439,7 +451,7 @@ void SpriteController::SpriteImpl::setAlpha(float alpha) { void SpriteController::SpriteImpl::setTransformationMatrix( const SpriteTransformationMatrix& matrix) { - AutoMutex _l(mController->mLock); + AutoMutex _l(mController.mLock); if (mLocked.state.transformationMatrix != matrix) { mLocked.state.transformationMatrix = matrix; @@ -448,7 +460,7 @@ void SpriteController::SpriteImpl::setTransformationMatrix( } void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) { - AutoMutex _l(mController->mLock); + AutoMutex _l(mController.mLock); if (mLocked.state.displayId != displayId) { mLocked.state.displayId = displayId; @@ -461,7 +473,7 @@ void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) { mLocked.state.dirty |= dirty; if (!wasDirty) { - mController->invalidateSpriteLocked(this); + mController.invalidateSpriteLocked(sp<SpriteImpl>::fromExisting(this)); } } diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 1f113c045360..04ecb3895aa2 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -109,15 +109,19 @@ public: * * Clients are responsible for animating sprites by periodically updating their properties. */ -class SpriteController : public MessageHandler { -protected: - virtual ~SpriteController(); - +class SpriteController { public: using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>; SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent); + SpriteController(const SpriteController&) = delete; + SpriteController& operator=(const SpriteController&) = delete; + virtual ~SpriteController(); - /* Creates a new sprite, initially invisible. */ + /* Initialize the callback for the message handler. */ + void setHandlerController(const std::shared_ptr<SpriteController>& controller); + + /* Creates a new sprite, initially invisible. The lifecycle of the sprite must not extend beyond + * the lifecycle of this SpriteController. */ virtual sp<Sprite> createSprite(); /* Opens or closes a transaction to perform a batch of sprite updates as part of @@ -129,9 +133,12 @@ public: virtual void closeTransaction(); private: - enum { - MSG_UPDATE_SPRITES, - MSG_DISPOSE_SURFACES, + class Handler : public virtual android::MessageHandler { + public: + enum { MSG_UPDATE_SPRITES, MSG_DISPOSE_SURFACES }; + + void handleMessage(const Message& message) override; + std::weak_ptr<SpriteController> spriteController; }; enum { @@ -192,7 +199,7 @@ private: virtual ~SpriteImpl(); public: - explicit SpriteImpl(const sp<SpriteController> controller); + explicit SpriteImpl(SpriteController& controller); virtual void setIcon(const SpriteIcon& icon); virtual void setVisible(bool visible); @@ -220,7 +227,7 @@ private: } private: - sp<SpriteController> mController; + SpriteController& mController; struct Locked { SpriteState state; @@ -245,7 +252,7 @@ private: sp<Looper> mLooper; const int32_t mOverlayLayer; - sp<WeakMessageHandler> mHandler; + sp<Handler> mHandler; ParentSurfaceProvider mParentSurfaceProvider; sp<SurfaceComposerClient> mSurfaceComposerClient; @@ -260,7 +267,6 @@ private: void invalidateSpriteLocked(const sp<SpriteImpl>& sprite); void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl); - void handleMessage(const Message& message); void doUpdateSprites(); void doDisposeSurfaces(); diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp index d9fe5996bcff..b8de919fbd8c 100644 --- a/libs/input/TouchSpotController.cpp +++ b/libs/input/TouchSpotController.cpp @@ -39,15 +39,15 @@ namespace android { // --- Spot --- -void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, float y, +void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY, int32_t displayId) { sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); sprite->setAlpha(alpha); sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); - sprite->setPosition(x, y); + sprite->setPosition(newX, newY); sprite->setDisplayId(displayId); - this->x = x; - this->y = y; + x = newX; + y = newY; if (icon != mLastIcon) { mLastIcon = icon; @@ -98,8 +98,8 @@ void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32 #endif std::scoped_lock lock(mLock); - sp<SpriteController> spriteController = mContext.getSpriteController(); - spriteController->openTransaction(); + auto& spriteController = mContext.getSpriteController(); + spriteController.openTransaction(); // Add or move spots for fingers that are down. for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { @@ -125,7 +125,7 @@ void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32 } } - spriteController->closeTransaction(); + spriteController.closeTransaction(); } void TouchSpotController::clearSpots() { @@ -167,7 +167,7 @@ TouchSpotController::Spot* TouchSpotController::createAndAddSpotLocked(uint32_t sprite = mLocked.recycledSprites.back(); mLocked.recycledSprites.pop_back(); } else { - sprite = mContext.getSpriteController()->createSprite(); + sprite = mContext.getSpriteController().createSprite(); } // Return the new spot. diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 85747514aa03..94faf4a65a1c 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -157,7 +157,7 @@ protected: sp<MockSprite> mPointerSprite; sp<MockPointerControllerPolicyInterface> mPolicy; - sp<MockSpriteController> mSpriteController; + std::unique_ptr<MockSpriteController> mSpriteController; std::shared_ptr<PointerController> mPointerController; private: @@ -175,14 +175,14 @@ private: PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>), mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) { - - mSpriteController = new NiceMock<MockSpriteController>(mLooper); + mSpriteController.reset(new NiceMock<MockSpriteController>(mLooper)); mPolicy = new MockPointerControllerPolicyInterface(); EXPECT_CALL(*mSpriteController, createSprite()) .WillOnce(Return(mPointerSprite)); - mPointerController = PointerController::create(mPolicy, mLooper, mSpriteController); + mPointerController = + PointerController::create(mPolicy, mLooper, *mSpriteController, /*enabled=*/true); } PointerControllerTest::~PointerControllerTest() { @@ -319,10 +319,10 @@ class PointerControllerWindowInfoListenerTest : public Test {}; class TestPointerController : public PointerController { public: TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener, - const sp<Looper>& looper) + const sp<Looper>& looper, SpriteController& spriteController) : PointerController( - new MockPointerControllerPolicyInterface(), looper, - new NiceMock<MockSpriteController>(looper), + new MockPointerControllerPolicyInterface(), looper, spriteController, + /*enabled=*/true, [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { // Register listener registeredListener = listener; @@ -335,10 +335,12 @@ public: TEST_F(PointerControllerWindowInfoListenerTest, doesNotCrashIfListenerCalledAfterPointerControllerDestroyed) { + sp<Looper> looper = new Looper(false); + auto spriteController = NiceMock<MockSpriteController>(looper); sp<android::gui::WindowInfosListener> registeredListener; sp<android::gui::WindowInfosListener> localListenerCopy; { - TestPointerController pointerController(registeredListener, new Looper(false)); + TestPointerController pointerController(registeredListener, looper, spriteController); ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered"; localListenerCopy = registeredListener; } diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp index 128be3c33ac5..28856c87f7c6 100644 --- a/libs/protoutil/Android.bp +++ b/libs/protoutil/Android.bp @@ -80,6 +80,10 @@ cc_test { "libgmock", ], + test_suites: [ + "general-tests", + ], + proto: { type: "full", }, diff --git a/libs/protoutil/AndroidTest.xml b/libs/protoutil/AndroidTest.xml deleted file mode 100644 index 46d418e1bb0a..000000000000 --- a/libs/protoutil/AndroidTest.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<configuration description="Config for libprotoutil_test"> - <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> - <option name="cleanup" value="true" /> - <option name="push" value="libprotoutil_test->/data/nativetest/libprotoutil_test" /> - </target_preparer> - <option name="test-suite-tag" value="apct" /> - <test class="com.android.tradefed.testtype.GTest" > - <option name="native-test-device-path" value="/data/nativetest" /> - <option name="module-name" value="libprotoutil_test" /> - </test> -</configuration> diff --git a/libs/protoutil/TEST_MAPPING b/libs/protoutil/TEST_MAPPING new file mode 100644 index 000000000000..b10dd9b067b6 --- /dev/null +++ b/libs/protoutil/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "libprotoutil_test" + } + ], + "hwasan-postsubmit": [ + { + "name": "libprotoutil_test" + } + ] +} diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h index f4a358de1c41..1bfb874729d7 100644 --- a/libs/protoutil/include/android/util/ProtoOutputStream.h +++ b/libs/protoutil/include/android/util/ProtoOutputStream.h @@ -102,7 +102,7 @@ public: bool write(uint64_t fieldId, long val); bool write(uint64_t fieldId, long long val); bool write(uint64_t fieldId, bool val); - bool write(uint64_t fieldId, std::string val); + bool write(uint64_t fieldId, std::string_view val); bool write(uint64_t fieldId, const char* val, size_t size); /** diff --git a/libs/protoutil/src/EncodedBuffer.cpp b/libs/protoutil/src/EncodedBuffer.cpp index 96b54c63a836..afb54a6c49b6 100644 --- a/libs/protoutil/src/EncodedBuffer.cpp +++ b/libs/protoutil/src/EncodedBuffer.cpp @@ -17,6 +17,7 @@ #include <stdlib.h> #include <sys/mman.h> +#include <unistd.h> #include <android/util/EncodedBuffer.h> #include <android/util/protobuf.h> @@ -25,7 +26,8 @@ namespace android { namespace util { -const size_t BUFFER_SIZE = 8 * 1024; // 8 KB +constexpr size_t BUFFER_SIZE = 8 * 1024; // 8 KB +const size_t kPageSize = getpagesize(); EncodedBuffer::Pointer::Pointer() : Pointer(BUFFER_SIZE) { @@ -92,7 +94,7 @@ EncodedBuffer::EncodedBuffer(size_t chunkSize) { // Align chunkSize to memory page size chunkSize = chunkSize == 0 ? BUFFER_SIZE : chunkSize; - mChunkSize = (chunkSize / PAGE_SIZE + ((chunkSize % PAGE_SIZE == 0) ? 0 : 1)) * PAGE_SIZE; + mChunkSize = (chunkSize + (kPageSize - 1)) & ~(kPageSize - 1); mWp = Pointer(mChunkSize); mEp = Pointer(mChunkSize); } diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp index fcf82eed4eb1..a44a1b210924 100644 --- a/libs/protoutil/src/ProtoOutputStream.cpp +++ b/libs/protoutil/src/ProtoOutputStream.cpp @@ -170,13 +170,13 @@ ProtoOutputStream::write(uint64_t fieldId, bool val) } bool -ProtoOutputStream::write(uint64_t fieldId, std::string val) +ProtoOutputStream::write(uint64_t fieldId, std::string_view val) { if (mCompact) return false; const uint32_t id = (uint32_t)fieldId; switch (fieldId & FIELD_TYPE_MASK) { case FIELD_TYPE_STRING: - writeUtf8StringImpl(id, val.c_str(), val.size()); + writeUtf8StringImpl(id, val.data(), val.size()); return true; default: ALOGW("Field type %" PRIu64 " is not supported when writing string val.", diff --git a/libs/protoutil/tests/EncodedBuffer_test.cpp b/libs/protoutil/tests/EncodedBuffer_test.cpp index f895154c4983..a09558544c26 100644 --- a/libs/protoutil/tests/EncodedBuffer_test.cpp +++ b/libs/protoutil/tests/EncodedBuffer_test.cpp @@ -15,12 +15,16 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <unistd.h> + using namespace android::util; using android::sp; -constexpr size_t TEST_CHUNK_SIZE = 16UL; -constexpr size_t TEST_CHUNK_HALF_SIZE = TEST_CHUNK_SIZE / 2; -constexpr size_t TEST_CHUNK_3X_SIZE = 3 * TEST_CHUNK_SIZE; +constexpr size_t __TEST_CHUNK_SIZE = 16UL; +const size_t kPageSize = getpagesize(); +const size_t TEST_CHUNK_SIZE = (__TEST_CHUNK_SIZE + (kPageSize - 1)) & ~(kPageSize - 1); +const size_t TEST_CHUNK_HALF_SIZE = TEST_CHUNK_SIZE / 2; +const size_t TEST_CHUNK_3X_SIZE = 3 * TEST_CHUNK_SIZE; static void expectPointer(EncodedBuffer::Pointer* p, size_t pos) { EXPECT_EQ(p->pos(), pos); @@ -34,13 +38,13 @@ TEST(EncodedBufferTest, WriteSimple) { expectPointer(buffer->wp(), 0); EXPECT_EQ(buffer->currentToWrite(), TEST_CHUNK_SIZE); for (size_t i = 0; i < TEST_CHUNK_HALF_SIZE; i++) { - buffer->writeRawByte(50 + i); + buffer->writeRawByte(static_cast<uint8_t>(50 + i)); } EXPECT_EQ(buffer->size(), TEST_CHUNK_HALF_SIZE); expectPointer(buffer->wp(), TEST_CHUNK_HALF_SIZE); EXPECT_EQ(buffer->currentToWrite(), TEST_CHUNK_HALF_SIZE); for (size_t i = 0; i < TEST_CHUNK_SIZE; i++) { - buffer->writeRawByte(80 + i); + buffer->writeRawByte(static_cast<uint8_t>(80 + i)); } EXPECT_EQ(buffer->size(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE); expectPointer(buffer->wp(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE); @@ -49,10 +53,10 @@ TEST(EncodedBufferTest, WriteSimple) { // verifies the buffer's data expectPointer(buffer->ep(), 0); for (size_t i = 0; i < TEST_CHUNK_HALF_SIZE; i++) { - EXPECT_EQ(buffer->readRawByte(), 50 + i); + EXPECT_EQ(buffer->readRawByte(), static_cast<uint8_t>(50 + i)); } for (size_t i = 0; i < TEST_CHUNK_SIZE; i++) { - EXPECT_EQ(buffer->readRawByte(), 80 + i); + EXPECT_EQ(buffer->readRawByte(), static_cast<uint8_t>(80 + i)); } // clears the buffer diff --git a/libs/services/Android.bp b/libs/services/Android.bp index f656ebfc3b77..0b7e7d37718d 100644 --- a/libs/services/Android.bp +++ b/libs/services/Android.bp @@ -26,8 +26,6 @@ package { cc_library_shared { name: "libservices", srcs: [ - ":IDropBoxManagerService.aidl", - ":ILogcatManagerService_aidl", "src/content/ComponentName.cpp", "src/os/DropBoxManager.cpp", ], @@ -42,7 +40,10 @@ cc_library_shared { "libbase_headers", ], aidl: { - include_dirs: ["frameworks/base/core/java/"], + libs: [ + "ILogcatManagerService_aidl", + "IDropBoxManagerService_aidl", + ], }, export_include_dirs: ["include"], diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp index 3716e019f69a..60bb00ab7309 100644 --- a/libs/services/src/os/DropBoxManager.cpp +++ b/libs/services/src/os/DropBoxManager.cpp @@ -196,7 +196,7 @@ DropBoxManager::addData(const String16& tag, uint8_t const* data, vector<uint8_t> dataArg; dataArg.assign(data, data + size); Status status = service->addData(tag, dataArg, flags); - ALOGD("service->add returned %s", status.toString8().string()); + ALOGD("service->add returned %s", status.toString8().c_str()); return status; } @@ -230,7 +230,7 @@ DropBoxManager::addFile(const String16& tag, int fd, int flags) android::base::unique_fd uniqueFd(fd); android::os::ParcelFileDescriptor parcelFd(std::move(uniqueFd)); Status status = service->addFile(tag, parcelFd, flags); - ALOGD("service->add returned %s", status.toString8().string()); + ALOGD("service->add returned %s", status.toString8().c_str()); return status; } |