diff options
Diffstat (limited to 'libs')
552 files changed, 19185 insertions, 5368 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..9da6c10c6d74 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -19,7 +19,6 @@ package androidx.window.extensions; import android.app.ActivityThread; import android.app.Application; import android.content.Context; -import android.window.TaskFragmentOrganizer; import androidx.annotation.NonNull; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; @@ -48,7 +47,7 @@ public class WindowExtensionsImpl implements WindowExtensions { // TODO(b/241126279) Introduce constants to better version functionality @Override public int getVendorApiLevel() { - return 3; + return 4; } @NonNull @@ -81,13 +80,7 @@ public class WindowExtensionsImpl implements WindowExtensions { Context context = getApplication(); DeviceStateManagerFoldingFeatureProducer producer = getFoldingFeatureProducer(); - // TODO(b/263263909) Use the organizer to tell if an Activity is embededed. - // Need to improve our Dependency Injection and centralize the logic. - TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(command -> { - throw new RuntimeException("Not allowed!"); - }); - mWindowLayoutComponent = new WindowLayoutComponentImpl(context, organizer, - producer); + mWindowLayoutComponent = new WindowLayoutComponentImpl(context, producer); } } } 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..381e9d472f0f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -32,7 +32,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 +46,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 +92,13 @@ class SplitContainer { } } + void setPrimaryContainer(@NonNull TaskFragmentContainer primaryContainer) { + if (!mIsPrimaryContainerMutable) { + throw new IllegalStateException("Cannot update primary TaskFragmentContainer"); + } + mPrimaryContainer = primaryContainer; + } + @NonNull TaskFragmentContainer getPrimaryContainer() { return mPrimaryContainer; 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..3ad30457697e 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; } @@ -1930,7 +2065,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 +2081,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 +2136,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 +2228,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..463c8ceaf992 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 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 mContainers.get(mContainers.size() - 1); + return null; } @Nullable @@ -207,6 +236,129 @@ class TaskContainer { return false; } + /** + * 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); + } + } + /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */ void getSplitStates(@NonNull List<SplitInfo> outSplitStates) { for (SplitContainer container : mSplitContainers) { 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..61df335515b8 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); 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..a1fe7f75a826 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; @@ -25,7 +24,7 @@ import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; import android.app.Activity; -import android.app.ActivityClient; +import android.app.ActivityThread; import android.app.Application; import android.app.WindowConfiguration; import android.content.ComponentCallbacks; @@ -35,8 +34,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; -import android.view.WindowManager; -import android.window.TaskFragmentOrganizer; +import android.util.Log; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; @@ -52,7 +50,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; /** @@ -64,7 +61,7 @@ import java.util.Set; * Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead. */ public class WindowLayoutComponentImpl implements WindowLayoutComponent { - private static final String TAG = "SampleExtension"; + private static final String TAG = WindowLayoutComponentImpl.class.getSimpleName(); private final Object mLock = new Object(); @@ -86,16 +83,15 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final Map<java.util.function.Consumer<WindowLayoutInfo>, Consumer<WindowLayoutInfo>> mJavaToExtConsumers = new ArrayMap<>(); - private final TaskFragmentOrganizer mTaskFragmentOrganizer; + private final RawConfigurationChangedListener mRawConfigurationChangedListener = + new RawConfigurationChangedListener(); public WindowLayoutComponentImpl(@NonNull Context context, - @NonNull TaskFragmentOrganizer taskFragmentOrganizer, @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) { ((Application) context.getApplicationContext()) .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); mFoldingFeatureProducer = foldingFeatureProducer; mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); - mTaskFragmentOrganizer = taskFragmentOrganizer; } /** Registers to listen to {@link CommonFoldingFeature} changes */ @@ -118,6 +114,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { final Consumer<WindowLayoutInfo> extConsumer = consumer::accept; synchronized (mLock) { mJavaToExtConsumers.put(consumer, extConsumer); + updateListenerRegistrations(); } addWindowLayoutInfoListener(activity, extConsumer); } @@ -171,6 +168,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { final Consumer<WindowLayoutInfo> extConsumer; synchronized (mLock) { extConsumer = mJavaToExtConsumers.remove(consumer); + updateListenerRegistrations(); } if (extConsumer != null) { removeWindowLayoutInfoListener(extConsumer); @@ -201,6 +199,17 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } @GuardedBy("mLock") + private void updateListenerRegistrations() { + ActivityThread currentThread = ActivityThread.currentActivityThread(); + if (mJavaToExtConsumers.isEmpty()) { + currentThread.removeConfigurationChangedListener(mRawConfigurationChangedListener); + } else { + currentThread.addConfigurationChangedListener(Runnable::run, + mRawConfigurationChangedListener); + } + } + + @GuardedBy("mLock") @NonNull private Set<Context> getContextsListeningForLayoutChanges() { return mWindowLayoutChangeListeners.keySet(); @@ -304,7 +313,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 +336,48 @@ 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) { + Log.wtf(TAG, "Bounding rectangle must start at the top or " + + "left of the window. BaseFeatureRect: " + baseFeature.getRect() + + ", FeatureRect: " + featureRect + + ", WindowConfiguration: " + windowConfiguration); + continue; + + } + if (featureRect.left == 0 + && featureRect.width() != windowConfiguration.getBounds().width()) { + Log.wtf(TAG, "Horizontal FoldingFeature must have full width." + + " BaseFeatureRect: " + baseFeature.getRect() + + ", FeatureRect: " + featureRect + + ", WindowConfiguration: " + windowConfiguration); + continue; + } + if (featureRect.top == 0 + && featureRect.height() != windowConfiguration.getBounds().height()) { + Log.wtf(TAG, "Vertical FoldingFeature must have full height." + + " BaseFeatureRect: " + baseFeature.getRect() + + ", FeatureRect: " + featureRect + + ", WindowConfiguration: " + windowConfiguration); + continue; } + features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); } return features; } @@ -357,38 +395,11 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { // Display features are not supported on secondary displays. return false; } - final int windowingMode; - IBinder activityToken = context.getActivityToken(); - if (activityToken != null) { - final Configuration taskConfig = ActivityClient.getInstance().getTaskConfiguration( - activityToken); - if (taskConfig == null) { - // If we cannot determine the task configuration for any reason, it is likely that - // we won't be able to determine its position correctly as well. DisplayFeatures' - // bounds in this case can't be computed correctly, so we should skip. - return false; - } - final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); - final WindowManager windowManager = Objects.requireNonNull( - context.getSystemService(WindowManager.class)); - final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds(); - boolean isTaskExpanded = maxBounds.equals(taskBounds); - /* - * We need to proxy being in full screen because when a user enters PiP and exits PiP - * the task windowingMode will report multi-window/pinned until the transition is - * finished in WM Shell. - * maxBounds == taskWindowBounds is a proxy check to verify the window is full screen - */ - return isTaskExpanded; - } else { - // TODO(b/242674941): use task windowing mode for window context that associates with - // activity. - windowingMode = context.getResources().getConfiguration().windowConfiguration - .getWindowingMode(); - } - // It is recommended not to report any display features in multi-window mode, since it - // won't be possible to synchronize the display feature positions with window movement. - return !WindowConfiguration.inMultiWindowMode(windowingMode); + + // We do not report folding features for Activities in PiP because the bounds are + // not updated fast enough and the window is too small for the UI to adapt. + return context.getResources().getConfiguration().windowConfiguration + .getWindowingMode() != WindowConfiguration.WINDOWING_MODE_PINNED; } @GuardedBy("mLock") @@ -417,6 +428,16 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } + private final class RawConfigurationChangedListener implements + java.util.function.Consumer<IBinder> { + @Override + public void accept(IBinder activityToken) { + synchronized (mLock) { + onDisplayFeaturesChangedIfListening(activityToken); + } + } + } + private final class ConfigurationChangeListener implements ComponentCallbacks { final IBinder mToken; 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..a0590dc2c832 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 @@ -29,7 +29,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 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..b2ffad7a74e4 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 @@ -1363,15 +1401,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 +1417,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 +1437,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 +1497,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..000c65a75c81 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 @@ -127,7 +127,7 @@ public class TaskContainerTest { assertFalse(taskContainer.isEmpty()); taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken()); - taskContainer.mContainers.clear(); + taskContainer.clearTaskFragmentContainer(); assertFalse(taskContainer.isEmpty()); } @@ -135,15 +135,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,13 +152,13 @@ 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); 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..603272e3df1d 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", } @@ -148,6 +151,7 @@ android_library { ], static_libs: [ "androidx.appcompat_appcompat", + "androidx.core_core-animation", "androidx.arch.core_core-runtime", "androidx-constraintlayout_constraintlayout", "androidx.dynamicanimation_dynamicanimation", 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/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/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/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/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 de4a225e41a7..4f763425b601 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index 14b0fd9db609..1e5f5f136315 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ገባኝ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ምንም የቅርብ ጊዜ አረፋዎች የሉም"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"የቅርብ ጊዜ አረፋዎች እና የተሰናበቱ አረፋዎች እዚህ ብቅ ይላሉ"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index a714b49d1305..9c52608a8d23 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"حسنًا"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ليس هناك فقاعات محادثات"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ستظهر هنا أحدث فقاعات المحادثات وفقاعات المحادثات التي تم إغلاقها."</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index 6d86747ef336..e880b8744f40 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index 7c662827cca7..6e746fb761f0 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -76,10 +76,14 @@ <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> + <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> 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 c415c868ac04..3be326907cd2 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 3d99514e2172..85ae1c10617c 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Зразумела"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Няма нядаўніх усплывальных апавяшчэнняў"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Нядаўнія і адхіленыя ўсплывальныя апавяшчэнні будуць паказаны тут"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index 0473f270239a..640fb2e0ef92 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Разбрах"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Няма скорошни балончета"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Скорошните и отхвърлените балончета ще се показват тук"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index 4fe1be0c455e..e7c8886a99be 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"বুঝেছি"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"কোনও সাম্প্রতিক বাবল নেই"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"সাম্প্রতিক ও বাতিল করা বাবল এখানে দেখা যাবে"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index b39b497f5c66..1335f8d897be 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index fe76e73135c9..22fc21c64e09 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index ac22b8569e16..a85fa7c9435d 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -76,10 +76,14 @@ <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> + <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">"Pokud je problém se zobrazením aplikace, klepněte na ni a restartujte ji."</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> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index c91cd7a956aa..cd621f802d91 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index c17f97fbad0f..366fdefc3013 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index e6a9aa67de18..9fbd127b3f80 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Το κατάλαβα"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Δεν υπάρχουν πρόσφατα συννεφάκια"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Τα πρόσφατα συννεφάκια και τα συννεφάκια που παραβλέψατε θα εμφανίζονται εδώ."</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml index ea91014298df..c7dd3882734b 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml index 01bdf95da918..99da073cb2c2 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -76,10 +76,14 @@ <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_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> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml index ea91014298df..c7dd3882734b 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml index ea91014298df..c7dd3882734b 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml index f6dac79d0379..cc19579c1216 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml @@ -76,10 +76,14 @@ <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_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> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index 0190ea85a30b..80d10f26e4e9 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index 9c5e0c48f6cb..13dfce03b775 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -76,10 +76,14 @@ <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> + <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 verlo mejor."</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> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index fb23d11ef74a..269968f48c77 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index 3ec522ef5ab2..6db0f8b1f23b 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index edff47a0be1a..434bfe11db4e 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"متوجهام"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"هیچ حبابک جدیدی وجود ندارد"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"حبابکهای اخیر و حبابکهای ردشده اینجا ظاهر خواهند شد"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index 92fa76013134..a04ef1252aa8 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 6d19e55217f6..fbc619144f71 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -76,10 +76,14 @@ <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> + <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">"Pour obtenir un meilleur affichage, touchez pour redémarrer cette application."</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> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index 5fb91f7b6948..e1fe2917a729 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -76,10 +76,14 @@ <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> + <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">"Pour un meilleur affichage, appuyez pour redémarrer cette appli."</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> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index c08cff86f63c..34fad3c269a6 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index 2a521991ef8d..365faef85ff6 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"સમજાઈ ગયું"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"તાજેતરના કોઈ બબલ નથી"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"એકદમ નવા બબલ અને છોડી દીધેલા બબલ અહીં દેખાશે"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index b0b0e9c6f5bc..76579d10b695 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ठीक है"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हाल ही के कोई बबल्स नहीं हैं"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हाल ही के बबल्स और हटाए गए बबल्स यहां दिखेंगे"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index 08721f0e6746..de071f1b959d 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -76,10 +76,14 @@ <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> + <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 za ponovno pokretanje te aplikacije i bolji prikaz."</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> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index 7566439ec7f8..b5631bbf0152 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 2b20870b1048..2d5d371df41e 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Եղավ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ամպիկներ չկան"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Այստեղ կցուցադրվեն վերջերս օգտագործված և փակված ամպիկները, որոնք կկարողանաք հեշտությամբ վերաբացել"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index 3f6d9c551aa6..90b1f152d035 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index 20c16be3eaca..813f9e6f4021 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -76,10 +76,14 @@ <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> + <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">"Ýttu 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> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index fd5f61ff718c..58601b7e88c5 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -76,10 +76,14 @@ <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> + <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 l\'app e migliorare la 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> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index bb3845b26a88..4d7a0936a3e0 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"הבנתי"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"אין בועות מהזמן האחרון"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"בועות אחרונות ובועות שנסגרו יופיעו כאן"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index 8f2f6d8e882f..96683590d097 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index e58e67ab36cb..a949a18fd449 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"გასაგებია"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ბოლო დროს გამოყენებული ბუშტები არ არის"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"აქ გამოჩნდება ბოლოდროინდელი ბუშტები და უარყოფილი ბუშტები"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index c40cd2fb24aa..cbc924907cac 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Түсінікті"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Жақындағы қалқыма хабарлар жоқ"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Соңғы және жабылған қалқыма хабарлар осы жерде көрсетіледі."</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 45302677a043..3e36113995d9 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"យល់ហើយ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"មិនមានពពុះថ្មីៗទេ"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ពពុះថ្មីៗ និងពពុះដែលបានបិទនឹងបង្ហាញនៅទីនេះ"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index 2dfbad49bb06..5e0dad8255b4 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ಅರ್ಥವಾಯಿತು"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಬಬಲ್ಸ್ ಇಲ್ಲ"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ಇತ್ತೀಚಿನ ಬಬಲ್ಸ್ ಮತ್ತು ವಜಾಗೊಳಿಸಿದ ಬಬಲ್ಸ್ ಇಲ್ಲಿ ಗೋಚರಿಸುತ್ತವೆ"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index 55697ca23a86..f1b34556954e 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"확인"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"최근 대화창 없음"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"최근 대화창과 내가 닫은 대화창이 여기에 표시됩니다."</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 19df267e1d27..200359aded72 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Түшүндүм"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Азырынча эч нерсе жок"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Акыркы жана жабылган калкып чыкма билдирмелер ушул жерде көрүнөт"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index a25699fa4d10..43835d5afa67 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ເຂົ້າໃຈແລ້ວ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ບໍ່ມີຟອງຫຼ້າສຸດ"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ຟອງຫຼ້າສຸດ ແລະ ຟອງທີ່ປິດໄປຈະປາກົດຢູ່ບ່ອນນີ້"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index d893fcf21d46..0c6cc58aa1ec 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index a1fbcddb1e3a..f86e937edd33 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index 1567d61d833f..49e850fac1c0 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Сфатив"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Нема неодамнешни балончиња"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Неодамнешните и отфрлените балончиња ќе се појавуваат тука"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index 5cca248e0dd4..fbb5514e4648 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"മനസ്സിലായി"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"അടുത്തിടെയുള്ള ബബിളുകൾ ഒന്നുമില്ല"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"അടുത്തിടെയുള്ള ബബിളുകൾ, ഡിസ്മിസ് ചെയ്ത ബബിളുകൾ എന്നിവ ഇവിടെ ദൃശ്യമാവും"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index 72e54fcf8711..8274f4456c57 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ойлголоо"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Саяхны бөмбөлөг алга байна"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Саяхны бөмбөлгүүд болон үл хэрэгссэн бөмбөлгүүд энд харагдана"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index a9e6319a2110..c1f3e123ac41 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"समजले"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"अलीकडील कोणतेही बबल नाहीत"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"अलीकडील बबल आणि डिसमिस केलेले बबल येथे दिसतील"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index b47531711863..82d84e8da6d9 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index cb6a1df95b33..2e88ab341984 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"နားလည်ပြီ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"လတ်တလော ပူဖောင်းကွက်များ မရှိပါ"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"လတ်တလော ပူဖောင်းကွက်များနှင့် ပိတ်လိုက်သော ပူဖောင်းကွက်များကို ဤနေရာတွင် မြင်ရပါမည်"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 6c80144fee18..f7ea9ce78dd8 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index f9f580530f8b..3f6dc046c10b 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"बुझेँ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हालैका बबलहरू छैनन्"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हालैका बबल र खारेज गरिएका बबलहरू यहाँ देखिने छन्"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 3064cccba7cc..978ed3ccad3c 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index 267b8a38a6d7..b66448b6308f 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ବୁଝିଗଲି"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ବର୍ତ୍ତମାନ କୌଣସି ବବଲ୍ ନାହିଁ"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ବର୍ତ୍ତମାନର ଏବଂ ଖାରଜ କରାଯାଇଥିବା ବବଲଗୁଡ଼ିକ ଏଠାରେ ଦେଖାଯିବ"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index d9f7f340a889..72cb92053e56 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ਸਮਝ ਲਿਆ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ਕੋਈ ਹਾਲੀਆ ਬਬਲ ਨਹੀਂ"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ਹਾਲੀਆ ਬਬਲ ਅਤੇ ਖਾਰਜ ਕੀਤੇ ਬਬਲ ਇੱਥੇ ਦਿਸਣਗੇ"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index 0699f5df99ab..24c1f1410fde 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index eea9be26f432..510d39eae966 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -76,10 +76,14 @@ <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> + <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 o tamanho da janela 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> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index a3d6ce5a4c5a..853c682b5089 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -76,10 +76,14 @@ <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> + <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 ver melhor."</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> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index eea9be26f432..510d39eae966 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -76,10 +76,14 @@ <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> + <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 o tamanho da janela 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> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index 58ad60a87982..7356f7cf08d2 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nu există baloane recente"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Baloanele recente și baloanele respinse vor apărea aici"</string> + <string name="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 afișare 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> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index a7db44dda901..61e3ec9b7da2 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ОК"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Нет недавних всплывающих чатов"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Здесь будут появляться недавние и скрытые всплывающие чаты."</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index 4153ce2bcf1f..ac78385b9644 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"තේරුණා"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"මෑත බුබුලු නැත"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"මෑත බුබුලු සහ ඉවත ලූ බුබුලු මෙහි දිස් වනු ඇත"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index 4e38943662ea..d659d51afc5f 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index b0e67a771653..91871fbf94ec 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index 6842fb32e6ba..4593c12763b0 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index 85798cf9f784..368df542796f 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Важи"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Нема недавних облачића"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Овде се приказују недавни и одбачени облачићи"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index 33652cd74fda..35d5b7af2b62 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index fe2ad1f1846d..52e0a6960948 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index 5bb4c27ed6c7..98a7d679014c 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"சரி"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"சமீபத்திய குமிழ்கள் இல்லை"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"சமீபத்திய குமிழ்களும் நிராகரிக்கப்பட்ட குமிழ்களும் இங்கே தோன்றும்"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index 6f95aa9c8305..70f810e21fcc 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"అర్థమైంది"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ఇటీవలి బబుల్స్ ఏవీ లేవు"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ఇటీవలి బబుల్స్ మరియు తీసివేసిన బబుల్స్ ఇక్కడ కనిపిస్తాయి"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index 6733940fd445..0efaab2d2f15 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"รับทราบ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ไม่มีบับเบิลเมื่อเร็วๆ นี้"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"บับเบิลที่แสดงและที่ปิดไปเมื่อเร็วๆ นี้จะปรากฏที่นี่"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index 8cf4eb484378..e5d535015c52 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index 1454435b3de7..8e7f1620354b 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index 78df129d8f50..5c7c6c4dae30 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Зрозуміло"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Немає нещодавніх спливаючих чатів"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Тут з\'являтимуться нещодавні й закриті спливаючі чати"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index ca1642488768..451d048ca825 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"سمجھ آ گئی"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"کوئی حالیہ بلبلہ نہیں"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"حالیہ بلبلے اور برخاست شدہ بلبلے یہاں ظاہر ہوں گے"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index c0dc03366cd6..4211ea7a5e7d 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index 0281c1c8c396..0a0205d8f591 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -76,10 +76,14 @@ <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> + <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 để có trải nghiệm 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> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index d1f50dba1ce1..3721abf24487 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -57,7 +57,7 @@ <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="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_content_description_single" msgid="8495748092720065813">"<xliff:g id="APP_NAME">%2$s</xliff:g>:<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> @@ -67,19 +67,23 @@ <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"移至左下角"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移至右下角"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>设置"</string> - <string name="bubble_dismiss_text" msgid="8816558050659478158">"关闭对话泡"</string> - <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"不以对话泡形式显示对话"</string> - <string name="bubbles_user_education_title" msgid="2112319053732691899">"使用对话泡聊天"</string> - <string name="bubbles_user_education_description" msgid="4215862563054175407">"新对话会以浮动图标或对话泡形式显示。点按即可打开对话泡。拖动即可移动对话泡。"</string> - <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"随时控制对话泡"</string> - <string name="bubbles_user_education_manage" msgid="3460756219946517198">"点按“管理”按钮,可关闭来自此应用的对话泡"</string> + <string name="bubble_dismiss_text" msgid="8816558050659478158">"关闭消息气泡"</string> + <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"不以消息气泡形式显示对话"</string> + <string name="bubbles_user_education_title" msgid="2112319053732691899">"使用消息气泡聊天"</string> + <string name="bubbles_user_education_description" msgid="4215862563054175407">"新对话会以浮动图标或消息气泡形式显示。点按即可打开消息气泡。拖动即可移动消息气泡。"</string> + <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"随时控制消息气泡"</string> + <string name="bubbles_user_education_manage" msgid="3460756219946517198">"点按“管理”按钮,可关闭来自此应用的消息气泡"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"知道了"</string> - <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近没有对话泡"</string> - <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"此处会显示最近的对话泡和已关闭的对话泡"</string> + <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近没有消息气泡"</string> + <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"此处会显示最近的消息气泡和已关闭的消息气泡"</string> + <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="accessibility_bubble_dismissed" msgid="8367471990421247357">"已关闭消息气泡。"</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> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index 3d33ecaccc38..0755d61e6a6d 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"知道了"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"沒有最近曾使用的小視窗"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"最近使用和關閉的小視窗會在這裡顯示"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index 4ca49e167118..f93188343fdf 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -76,10 +76,14 @@ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"我知道了"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近沒有任何對話框"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"最近的對話框和已關閉的對話框會顯示在這裡"</string> + <string name="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> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index 478b5a62c8a5..3ba0abee2a95 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -76,10 +76,14 @@ <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> + <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> diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml index 171a6b2fe5fb..b2ec98bc1b15 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> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 2be34c90a661..99526de56e4e 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> @@ -398,7 +428,15 @@ <!-- 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> </resources> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index b192fdf245e2..00c63d70d3a0 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -163,6 +163,11 @@ <!-- [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 "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 +178,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/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java index edefe9e3ab06..74a243d34642 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 @@ -371,7 +371,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/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 102f2cb4b8d0..9a2b81243861 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); 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..a70cf001a88b 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 @@ -25,7 +25,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 +36,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; @@ -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; @@ -143,16 +144,6 @@ public class BubbleController implements ConfigurationChangeListener, 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); - - /** * Common interface to send updates to bubble views. */ @@ -195,6 +186,7 @@ public class BubbleController implements ConfigurationChangeListener, 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; @@ -291,7 +283,8 @@ public class BubbleController implements ConfigurationChangeListener, @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue, - IWindowManager wmService) { + IWindowManager wmService, + BubbleProperties bubbleProperties) { mContext = context; mShellCommandHandler = shellCommandHandler; mShellController = shellController; @@ -328,6 +321,7 @@ public class BubbleController implements ConfigurationChangeListener, mDragAndDropController = dragAndDropController; mSyncQueue = syncQueue; mWmService = wmService; + mBubbleProperties = bubbleProperties; shellInit.addInitCallback(this::onInit, this); } @@ -518,11 +512,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 +528,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 +648,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 +726,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 +781,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 +796,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 +818,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); + } } } @@ -993,9 +1005,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 +1052,20 @@ 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); + } + } + @VisibleForTesting public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) { boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key) @@ -1065,9 +1089,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; @@ -1220,6 +1254,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 +1300,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 +1321,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 +1451,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); @@ -1738,7 +1836,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 +1944,7 @@ public class BubbleController implements ConfigurationChangeListener, if (mStackView != null) { mStackView.setVisibility(VISIBLE); } - if (mLayerView != null && isStackExpanded()) { + if (mLayerView != null) { mLayerView.setVisibility(VISIBLE); } } @@ -1860,10 +1958,17 @@ public class BubbleController implements ConfigurationChangeListener, } @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 @@ -2002,27 +2107,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) { + mMainExecutor.execute( + () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE)); } @Override - public void removeBubble(String key, int reason) { - // TODO (b/271466616) allow removals from launcher + public void removeAllBubbles() { + mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE)); } @Override @@ -2031,8 +2139,8 @@ 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)); } } @@ -2144,7 +2252,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/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..76662c47238f 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 @@ -47,12 +47,16 @@ public class BubbleDebugConfig { 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,6 +67,14 @@ 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) { 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..e6986012dd88 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,10 +1087,7 @@ 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); } } 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..52c9bf8462ec 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,11 @@ 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.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -46,7 +46,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 +74,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 +88,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 +107,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,7 +131,7 @@ public class BubbleStackView extends FrameLayout private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150; - private static final float SCRIM_ALPHA = 0.6f; + private static final float SCRIM_ALPHA = 0.32f; /** Minimum alpha value for scrim when alpha is being changed via drag */ private static final float MIN_SCRIM_ALPHA_FOR_DRAG = 0.2f; @@ -307,6 +305,7 @@ public class BubbleStackView extends FrameLayout String bubblesOnScreen = BubbleDebugConfig.formatBubblesString( getBubblesOnScreen(), getExpandedBubble()); + pw.print(" stack visibility : "); pw.println(getVisibility()); pw.print(" bubbles on screen: "); pw.println(bubblesOnScreen); pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress); pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing()); @@ -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 */); @@ -2019,9 +2043,7 @@ public class BubbleStackView extends FrameLayout * 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()) { @@ -2041,9 +2063,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(); } @@ -3411,6 +3431,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..39e3180ffe2a 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); + info.bubbleBarExpandedView.initialize(controller, 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; } - 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,83 @@ 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; + } + + Drawable bubbleDrawable = null; + try { + // Badged bubble image + bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, + b.getIcon()); + } catch (Exception e) { + // If we can't create the icon we'll default to the app icon + Log.w(TAG, "Exception creating icon for the bubble: " + b.getKey()); + } + + 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); + 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/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt new file mode 100644 index 000000000000..ed3624035757 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt @@ -0,0 +1,33 @@ +/* + * 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( + 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..4dda0688b790 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,14 @@ 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; }
\ 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..8f11253290ea 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 @@ -24,14 +24,18 @@ import android.content.Context; 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; + /** * 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 +52,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 +70,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 +81,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 +95,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 +111,7 @@ public class BubbleBarLayerView extends FrameLayout getViewTreeObserver().removeOnComputeInternalInsetsListener(this); if (mExpandedView != null) { + mEducationViewController.hideManageEducation(/* animated = */ false); removeView(mExpandedView); mExpandedView = null; } @@ -141,17 +154,55 @@ 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() { + 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)); } 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 +210,38 @@ public class BubbleBarLayerView extends FrameLayout public void collapse() { mIsExpanded = false; final BubbleBarExpandedView viewToRemove = mExpandedView; + mEducationViewController.hideManageEducation(/* animated = */ true); mAnimationHelper.animateCollapse(() -> removeView(viewToRemove)); mBubbleController.getSysuiProxy().onStackExpandChanged(false); mExpandedView = null; + setTouchDelegate(null); showScrim(false); } + /** 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.isManageEducationVisible()) { + mEducationViewController.hideManageEducation(/* 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 +251,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(); } 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..7b39c6fd4059 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt @@ -0,0 +1,148 @@ +/* + * 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.LayoutInflater +import android.view.ViewGroup +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.BubbleEducationController +import com.android.wm.shell.bubbles.BubbleViewProvider +import com.android.wm.shell.bubbles.setup +import com.android.wm.shell.common.bubbles.BubblePopupView + +/** Manages bubble education presentation and animation */ +class BubbleEducationViewController(private val context: Context, private val listener: Listener) { + interface Listener { + fun onManageEducationVisibilityChanged(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 controller by lazy { BubbleEducationController(context) } + + /** Whether the education view is visible or being animated */ + val isManageEducationVisible: Boolean + get() = educationView != null && rootView != null + + /** + * 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) { + if (!controller.shouldShowManageEducation(bubble)) return + showManageEducation(root) + } + + /** + * Hide the manage education view if visible + * + * @param animated whether should hide with animation + */ + fun hideManageEducation(animated: Boolean) { + rootView?.let { + fun cleanUp() { + it.removeView(educationView) + rootView = null + listener.onManageEducationVisibilityChanged(isVisible = false) + } + + if (animated) { + animateTransition(show = false, ::cleanUp) + } else { + cleanUp() + } + } + } + + /** + * Show manage education with animation + * + * @param root the view to show manage education in + */ + private fun showManageEducation(root: ViewGroup) { + hideManageEducation(animated = false) + if (educationView == null) { + val eduView = createEducationView(root) + educationView = eduView + animator = createAnimation(eduView) + } + root.addView(educationView) + rootView = root + animateTransition(show = true) { + controller.hasSeenManageEducation = true + listener.onManageEducationVisibilityChanged(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?.let { animator -> + 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() + } + + private fun createEducationView(root: ViewGroup): BubblePopupView { + val view = + LayoutInflater.from(context).inflate(R.layout.bubble_bar_manage_education, root, false) + as BubblePopupView + + return view.apply { + setup() + alpha = 0f + pivotY = 0f + scaleX = EDU_SCALE_HIDDEN + scaleY = EDU_SCALE_HIDDEN + doOnLayout { it.pivotX = it.width / 2f } + setOnClickListener { hideManageEducation(animated = true) } + } + } + + private fun createAnimation(view: BubblePopupView): PhysicsAnimator<BubblePopupView> { + val animator = PhysicsAnimator.getInstance(view) + animator.setDefaultSpringConfig(springConfig) + return animator + } + + companion object { + 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/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt new file mode 100644 index 000000000000..85aaa8ef585c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.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.bubbles.properties + +/** + * 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/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt new file mode 100644 index 000000000000..9d8b9a6f3260 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt @@ -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. + */ + +package com.android.wm.shell.bubbles.properties + +import android.os.SystemProperties + +/** 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/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/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..1fd22d0a3505 --- /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(private 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..f8a4946bb5c5 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.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.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) { + private var popupDrawable: BubblePopupDrawable? = null + + /** + * 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..d275a0be8e93 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,73 @@ * 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( + /** 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 +93,41 @@ 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.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 +135,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 +155,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 +176,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 +201,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..1901e0bbe700 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java @@ -0,0 +1,476 @@ +/* + * 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 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, SnapTarget.FLAG_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, SnapTarget.FLAG_DISMISS_START, + 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, SnapTarget.FLAG_DISMISS_END, 0.35f)); + } + + private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition, + int bottomPosition, int dividerMax) { + maybeAddTarget(topPosition, topPosition - getStartInset()); + addMiddleTarget(isHorizontalDivision); + maybeAddTarget(bottomPosition, + dividerMax - getEndInset() - (bottomPosition + mDividerSize)); + } + + 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) { + if (smallerSize >= mMinimalSizeResizableTask) { + mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE)); + } + } + + private void addMiddleTarget(boolean isHorizontalDivision) { + int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, + mInsets, mDisplayWidth, mDisplayHeight, mDividerSize); + mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE)); + } + + 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, SnapTarget.FLAG_NONE)); + } + + 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 { + public static final int FLAG_NONE = 0; + + /** If the divider reaches this value, the left/top task should be dismissed. */ + public static final int FLAG_DISMISS_START = 1; + + /** If the divider reaches this value, the right/bottom task should be dismissed */ + public static final int FLAG_DISMISS_END = 2; + + /** 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; + + public final int flag; + + 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, int flag) { + this(position, taskPosition, flag, 1f); + } + + public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) { + this.position = position; + this.taskPosition = taskPosition; + this.flag = flag; + 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..755dba0c895f 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 @@ -24,9 +24,10 @@ 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.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END; +import static com.android.wm.shell.common.split.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.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -58,8 +59,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; @@ -593,9 +592,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 +662,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 +733,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 +777,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 +791,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..be1b9b1227de 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; @@ -60,7 +61,8 @@ public class SplitScreenConstants { 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..953efa78326c 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,20 @@ 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.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.hardware.display.DisplayManager; +import android.net.Uri; +import android.os.UserHandle; +import android.provider.Settings; import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -29,6 +37,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 +50,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 +63,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 +85,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 +116,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 +140,67 @@ 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) { + /** + * The id of the task for the application we're currently attempting to show the user aspect + * ratio settings button for, or have most recently shown the button for. + */ + private int mTopActivityTaskId; + + /** + * Whether the user aspect ratio settings button has been shown for the current application + * associated with the task id stored in {@link CompatUIController#mTopActivityTaskId}. + */ + private boolean mHasShownUserAspectRatioSettingsButton = false; + + 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 +213,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 +225,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,11 +237,16 @@ 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); } + + if (taskInfo != null && taskListener != null) { + updateActiveTaskInfo(taskInfo); + } + if (taskInfo.configuration == null || taskListener == null) { // Null token means the current foreground activity is not in compatibility mode. removeLayouts(taskInfo.taskId); @@ -203,6 +258,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); + } } } @@ -272,6 +339,46 @@ public class CompatUIController implements OnDisplaysChangedListener, forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId()))); } + /** + * Invoked when a new task is created or the info of an existing task has changed. Updates the + * shown status of the user aspect ratio settings button and the task id it relates to. + */ + void updateActiveTaskInfo(@NonNull TaskInfo taskInfo) { + // If the activity belongs to the task we are currently tracking, don't update any variables + // as they are still relevant. Else, if the activity is visible and focused (the one the + // user can see and is using), the user aspect ratio button can potentially be displayed so + // start tracking the buttons visibility for this task. + if (mTopActivityTaskId != taskInfo.taskId && !taskInfo.isTopActivityTransparent + && taskInfo.isVisible && taskInfo.isFocused) { + mTopActivityTaskId = taskInfo.taskId; + setHasShownUserAspectRatioSettingsButton(false); + } + } + + /** + * Informs the system that the user aspect ratio button has been displayed for the application + * associated with the task id in {@link CompatUIController#mTopActivityTaskId}. + */ + void setHasShownUserAspectRatioSettingsButton(boolean state) { + mHasShownUserAspectRatioSettingsButton = state; + } + + /** + * Returns whether the user aspect ratio settings button has been show for the application + * associated with the task id in {@link CompatUIController#mTopActivityTaskId}. + */ + boolean hasShownUserAspectRatioSettingsButton() { + return mHasShownUserAspectRatioSettingsButton; + } + + /** + * Returns the task id of the application we are currently attempting to show, of have most + * recently shown, the user aspect ratio settings button for. + */ + int getTopActivityTaskId() { + return mTopActivityTaskId; + } + private boolean showOnDisplay(int displayId) { return !mKeyguardShowing && !isImeShowingOnDisplay(displayId); } @@ -280,8 +387,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 +421,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 +435,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 +449,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 +479,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 +530,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 +539,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 +550,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 +581,76 @@ 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, this::hasShownUserAspectRatioSettingsButton, + this::setHasShownUserAspectRatioSettingsButton); } + 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); + final ComponentName appComponent = taskInfo.topActivity; + if (appComponent != null) { + final Uri packageUri = Uri.parse("package:" + appComponent.getPackageName()); + intent.setData(packageUri); + } + final UserHandle userHandle = UserHandle.of(taskInfo.userId); + mContext.startActivityAsUser(intent, userHandle); + } 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 +671,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 +732,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 +770,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..b141bebbe8b1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java @@ -0,0 +1,179 @@ +/* + * 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.AnimatorSet; +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.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; +import android.view.animation.PathInterpolator; +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 Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); + + private static final Interpolator PATH_INTERPOLATOR = + new PathInterpolator(0.2f, 0f, 0f, 1f); + + private static final float ALPHA_FULL_TRANSPARENT = 0f; + + private static final float ALPHA_FULL_OPAQUE = 1f; + + private static final float SCALE_START = 0.8f; + + private static final float SCALE_END = 1f; + + private static final long FADE_ANIMATION_DURATION_MS = 167; + + private static final long SCALE_ANIMATION_DURATION_MS = 300; + + private static final String ALPHA_PROPERTY_NAME = "alpha"; + + private static final String SCALE_X_PROPERTY_NAME = "scaleX"; + + private static final String SCALE_Y_PROPERTY_NAME = "scaleY"; + + 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 { + hideItem(view); + } + } + + @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) { + final AnimatorSet animatorSet = new AnimatorSet(); + final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME, + ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE); + fadeIn.setDuration(FADE_ANIMATION_DURATION_MS); + fadeIn.setInterpolator(LINEAR_INTERPOLATOR); + final ObjectAnimator scaleY = + ObjectAnimator.ofFloat(view, SCALE_Y_PROPERTY_NAME, SCALE_START, SCALE_END); + final ObjectAnimator scaleX = + ObjectAnimator.ofFloat(view, SCALE_X_PROPERTY_NAME, SCALE_START, SCALE_END); + scaleX.setDuration(SCALE_ANIMATION_DURATION_MS); + scaleX.setInterpolator(PATH_INTERPOLATOR); + scaleY.setDuration(SCALE_ANIMATION_DURATION_MS); + scaleY.setInterpolator(PATH_INTERPOLATOR); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + view.setVisibility(View.VISIBLE); + } + }); + animatorSet.playTogether(fadeIn, scaleY, scaleX); + animatorSet.start(); + } + + private void hideItem(@NonNull View view) { + final ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME, + ALPHA_FULL_OPAQUE, ALPHA_FULL_TRANSPARENT); + fadeOut.setDuration(FADE_ANIMATION_DURATION_MS); + fadeOut.setInterpolator(LINEAR_INTERPOLATOR); + fadeOut.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(View.GONE); + } + }); + fadeOut.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..c2dec623416b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -0,0 +1,239 @@ +/* + * 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.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * 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; + + @NonNull + private final Supplier<Boolean> mUserAspectRatioButtonShownChecker; + + @NonNull + private final Consumer<Boolean> mUserAspectRatioButtonStateConsumer; + + @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, + @NonNull Supplier<Boolean> userAspectRatioButtonStateChecker, + @NonNull Consumer<Boolean> userAspectRatioButtonShownConsumer) { + super(context, taskInfo, syncQueue, taskListener, displayLayout); + mShellExecutor = shellExecutor; + mUserAspectRatioButtonShownChecker = userAspectRatioButtonStateChecker; + mUserAspectRatioButtonStateConsumer = userAspectRatioButtonShownConsumer; + 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); + } + } + + @VisibleForTesting + boolean isShowingButton() { + return (mUserAspectRatioButtonShownChecker.get() + && !isHideDelayReached(mNextButtonHideTimeMs)); + } + + private void showUserAspectRatioButton() { + if (mLayout == null) { + return; + } + mLayout.setUserAspectRatioButtonVisibility(true); + mUserAspectRatioButtonStateConsumer.accept(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) + && (!mUserAspectRatioButtonShownChecker.get() || isShowingButton()); + } + + 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..9facbd542e6c 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; @@ -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..45869e177c6d 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; @@ -46,6 +47,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,6 +58,15 @@ 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; @@ -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; @@ -102,13 +108,13 @@ import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import com.android.wm.shell.windowdecor.WindowDecorViewModel; -import java.util.Optional; - import dagger.BindsOptionalOf; import dagger.Lazy; import dagger.Module; import dagger.Provides; +import java.util.Optional; + /** * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only * accessible from components within the WM subcomponent (can be explicitly exposed to the @@ -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 // @@ -311,6 +332,60 @@ public abstract class WMShellBaseModule { return Optional.empty(); } + // + // 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 +537,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 // @@ -829,8 +870,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..065e7b09a05d 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; @@ -36,25 +37,29 @@ import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; 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.dagger.pip.PipModule; 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; 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 +67,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; @@ -122,7 +107,10 @@ import java.util.Optional; * 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 +}) public abstract class WMShellModule { // @@ -180,11 +168,12 @@ public abstract class WMShellModule { 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, syncQueue, wmService, ProdBubbleProperties.INSTANCE); } // @@ -197,33 +186,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) { + Optional<DesktopTasksController> desktopTasksController) { if (DesktopModeStatus.isAnyEnabled()) { return new DesktopModeWindowDecorViewModel( context, mainHandler, mainChoreographer, + shellInit, taskOrganizer, displayController, + shellController, syncQueue, transitions, desktopModeController, - desktopTasksController, - splitScreenController); + desktopTasksController); } return new CaptionWindowDecorViewModel( - context, - mainHandler, - mainChoreographer, - taskOrganizer, - displayController, - syncQueue); + context, + mainHandler, + mainChoreographer, + taskOrganizer, + displayController, + syncQueue); } // @@ -263,8 +254,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 +323,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 +343,16 @@ public abstract class WMShellModule { static DefaultMixedHandler provideDefaultMixedHandler( ShellInit shellInit, Optional<SplitScreenController> splitScreenOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional, + @Nullable PipTransitionController pipTransitionController, Optional<RecentsTransitionHandler> recentsTransitionHandler, KeyguardTransitionHandler keyguardTransitionHandler, + Optional<DesktopModeController> desktopModeController, + Optional<DesktopTasksController> desktopTasksController, Optional<UnfoldTransitionHandler> unfoldHandler, Transitions transitions) { return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional, - pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler, + pipTransitionController, recentsTransitionHandler, + keyguardTransitionHandler, desktopModeController, desktopTasksController, unfoldHandler); } @@ -573,13 +387,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 @@ -671,6 +485,7 @@ public abstract class WMShellModule { static DesktopTasksController provideDesktopTasksController( Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, ShellTaskOrganizer shellTaskOrganizer, @@ -679,13 +494,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 +515,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 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 index b9d2be280efb..5b24d7a60c4e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -33,6 +33,7 @@ import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DE import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; import android.content.Context; +import android.content.res.TypedArray; import android.database.ContentObserver; import android.graphics.Region; import android.net.Uri; @@ -414,6 +415,25 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll } /** + * 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. + */ + public void syncSurfaceState(@NonNull TransitionInfo info, + SurfaceControl.Transaction finishTransaction) { + // Add rounded corners to freeform windows + final TypedArray ta = mContext.obtainStyledAttributes( + new int[]{android.R.attr.dialogCornerRadius}); + final int cornerRadius = ta.getDimensionPixelSize(0, 0); + ta.recycle(); + for (TransitionInfo.Change change: info.getChanges()) { + if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) { + finishTransaction.setCornerRadius(change.getLeash(), cornerRadius); + } + } + } + + /** * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE} */ private final class SettingsObserver extends ContentObserver { @@ -500,6 +520,11 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll } @Override + public void showDesktopApp(int taskId) throws RemoteException { + // TODO + } + + @Override public int getVisibleTaskCount(int displayId) throws RemoteException { int[] result = new int[1]; executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount", @@ -508,5 +533,25 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll ); return result[0]; } + + @Override + public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) { + + } + + @Override + public void stashDesktopApps(int displayId) throws RemoteException { + // Stashing of desktop apps not needed. Apps always launch on desktop + } + + @Override + public void hideStashedDesktopApps(int displayId) throws RemoteException { + // Stashing of desktop apps not needed. Apps always launch on desktop + } + + @Override + public void setTaskListener(IDesktopTaskListener listener) throws RemoteException { + // TODO(b/261234402): move visibility from sysui state to listener + } } } 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..517f9f2aba27 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 @@ -54,6 +54,15 @@ public class DesktopModeStatus { public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean( "persist.wm.debug.desktop_change_display", false); + + /** + * 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. + */ + private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean( + "persist.wm.debug.desktop_stashing", false); + /** * Return {@code true} if desktop mode support is enabled */ @@ -84,6 +93,13 @@ public class DesktopModeStatus { } /** + * 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 isStashingEnabled() { + return IS_STASHING_ENABLED; + } + /** * Check if desktop mode is active * * @return {@code true} if active 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..711df0d89936 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). @@ -85,8 +87,10 @@ 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) } } } @@ -312,6 +316,52 @@ class DesktopModeTaskRepository { } /** + * 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 { @@ -331,5 +381,15 @@ class DesktopModeTaskRepository { */ @JvmDefault fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {} + + /** + * Called when the desktop stashed status changes. + */ + @JvmDefault + fun onStashedChanged(displayId: Int, stashed: Boolean) {} } } + +private fun <T> Iterable<T>.toDumpString(): String { + return joinToString(separator = ", ", prefix = "[", postfix = "]") +}
\ No newline at end of file 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..4e418e11d226 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 @@ -16,19 +16,25 @@ package com.android.wm.shell.desktopmode +import android.R import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration 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.content.res.TypedArray 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,7 +42,6 @@ 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.wm.shell.RootTaskDisplayAreaOrganizer @@ -44,18 +49,28 @@ 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.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 +78,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 +87,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,6 +101,22 @@ 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() @@ -92,12 +127,14 @@ class DesktopTasksController( 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 +155,139 @@ 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() // 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() 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 +302,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, WindowConfiguration.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 { @@ -213,21 +329,30 @@ class DesktopTasksController( } /** - * 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 +365,7 @@ class DesktopTasksController( task.taskId ) val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task.token) + addMoveToFullscreenChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { exitDesktopTaskTransitionHandler.startTransition( @@ -252,6 +377,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 +461,49 @@ 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) + } + } + + 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 +570,205 @@ 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 + } - // 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( + /** + * 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 ta: TypedArray = context.obtainStyledAttributes( + intArrayOf(R.attr.dialogCornerRadius)) + val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat() + ta.recycle() + info.changes + .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM } + .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) } + } + + 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()) } } - private fun getFullscreenDensityDpi(): Int { + /** + * 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 getDefaultDensityDpi(): Int { return context.resources.displayMetrics.densityDpi } @@ -497,26 +788,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 +825,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 +874,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 +905,7 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, freeformBounds: Rect ) { - moveToDesktopWithAnimation(taskInfo, freeformBounds) + finalizeMoveToDesktop(taskInfo, freeformBounds) } private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int { @@ -632,6 +957,12 @@ class DesktopTasksController( desktopModeTaskRepository.setTaskCornerListener(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. */ @ExternalThread private inner class DesktopModeImpl : DesktopMode { @@ -658,8 +989,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 +1044,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 +1075,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 */ 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..1128d91bb5ad 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,207 @@ 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/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl new file mode 100644 index 000000000000..39128a863ec9 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl @@ -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.desktopmode; + +/** + * 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); + + /** 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/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..53b5bd7ceb94 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; @@ -73,6 +76,10 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler private IRemoteTransition mOccludeByDreamTransition = null; private IRemoteTransition mUnoccludeTransition = null; + // While set true, Keyguard has created a remote animation runner to handle the open app + // transition. + private boolean mIsLaunchingActivityOverLockscreen; + private final class StartedTransition { final TransitionInfo mInfo; final SurfaceControl.Transaction mFinishT; @@ -117,7 +124,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback) { - if (!handles(info)) { + if (!handles(info) || mIsLaunchingActivityOverLockscreen) { return false; } @@ -152,9 +159,15 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback) { + + if (remoteHandler == null) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "missing handler for keyguard %s transition", description); + return false; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "start keyguard %s transition, info = %s", description, info); - try { mStartedTransitions.put(transition, new StartedTransition(info, finishTransaction, remoteHandler)); @@ -166,10 +179,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 +225,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 +280,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( @@ -284,5 +323,11 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler mUnoccludeTransition = unoccludeTransition; }); } + + @Override + public void setLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) { + mMainExecutor.execute(() -> + mIsLaunchingActivityOverLockscreen = isLaunchingActivityOverLockscreen); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java index b4b327f0eff2..33c299f0b161 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java @@ -38,4 +38,9 @@ public interface KeyguardTransitions { @NonNull IRemoteTransition occludeTransition, @NonNull IRemoteTransition occludeByDreamTransition, @NonNull IRemoteTransition unoccludeTransition) {} + + /** + * Notify whether keyguard has created a remote animation runner for next app launch. + */ + default void setLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) {} } 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..bc17ce97dbff 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; 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 bbd17cd67dc7..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 @@ -947,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, @@ -1008,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); @@ -1212,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); } @@ -1261,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..700a31926d6c 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); 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..4bba9690707a 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; 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..47a8cc89559e 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,9 +76,9 @@ 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(); mPreviousCollapsedGravity = mDefaultGravity; 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..46336ce0655a 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; 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/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index 613791ccc062..45e1cde8f9a9 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; 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..ec09827fa4d1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS @@ -0,0 +1,3 @@ +# WM shell sub-module pip owner +hwwang@google.com +mateuszc@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/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 843e5af67af9..50ba8975802b 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 @@ -219,6 +219,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 +376,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } mFinishTransaction = null; mPausingTasks = null; + mClosingTasks = null; mOpeningTasks = null; mInfo = null; mTransition = null; @@ -413,6 +421,7 @@ 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; @@ -567,15 +576,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 +602,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 +613,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 +624,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 +668,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 +707,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 +735,23 @@ 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); + if (wasClosing) { + // App was previously visible and is closing + t.show(target.leash); + t.setAlpha(target.leash, 1f); + // Also override the task alpha as it was set earlier when dispatching + // the transition and setting up the leash to hide the + t.setAlpha(change.getLeash(), 1f); + } else { + // Hide the animation leash, let the listener show it + t.hide(target.leash); + } 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 +799,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 +965,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..f90ee586e696 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 @@ -31,6 +31,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; @@ -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 @@ -171,6 +177,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 +206,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 +225,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 +257,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 +277,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 +312,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 +431,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 +476,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 +503,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 +584,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 +615,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 +649,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 +686,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 +718,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 +748,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 +768,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 @@ -766,6 +839,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; @@ -1043,6 +1118,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 +1133,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 +1179,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/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 7699e2f7c13d..1c792395d22d 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 @@ -23,11 +23,13 @@ import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVIT 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.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; 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 +43,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 +84,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 +123,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 +141,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 +188,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 +200,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 +222,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 +290,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 +300,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 +312,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStageListener, mSyncQueue, mSurfaceSession, - iconProvider); + iconProvider, + mWindowDecorViewModel); mSideStage = new SideStage( mContext, mTaskOrganizer, @@ -296,7 +321,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStageListener, mSyncQueue, mSurfaceSession, - iconProvider); + iconProvider, + mWindowDecorViewModel); mDisplayController = displayController; mDisplayImeController = displayImeController; mDisplayInsetsController = displayInsetsController; @@ -323,7 +349,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 +368,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, @@ -363,6 +393,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mMainStage.isActive(); } + /** @return whether this transition-request has the launch-adjacent flag. */ + public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) { + final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); + return triggerTask != null && triggerTask.baseIntent != null + && (triggerTask.baseIntent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0; + } + /** @return whether the transition-request implies entering pip from split. */ public boolean requestImpliesSplitToPip(TransitionRequestInfo request) { if (!isSplitActive() || !mMixedHandler.requestHasPipEnter(request)) { @@ -445,6 +482,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 +506,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 +595,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(); } @@ -675,6 +726,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setDivideRatio(splitRatio); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); + wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, + false /* reparentLeafTaskIfRelaunch */); setRootForceTranslucent(false, wct); // Make sure the launch options will put tasks in the corresponding split roots @@ -718,12 +771,14 @@ 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); + wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, + false /* reparentLeafTaskIfRelaunch */); setRootForceTranslucent(false, wct); - setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); if (shortcutInfo1 != null) { @@ -1076,6 +1131,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 +1152,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 +1338,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 +1399,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 +1527,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 @@ -1490,6 +1563,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, boolean resizeAnim) { onSplitScreenEnter(); + // Preemptively reset the reparenting behavior if we know that we are entering, as starting + // split tasks with activity trampolines can inadvertently trigger the task to be + // reparented out of the split root mid-launch + wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, + false /* setReparentLeafTaskIfRelaunch */); if (isSplitActive()) { prepareBringSplit(wct, taskInfo, startPosition, resizeAnim); } else { @@ -1547,6 +1625,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 +1696,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 +1821,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 +1868,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 +1875,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 +1905,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); } @@ -2159,6 +2244,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } + /** + * Returns the {@link StageType} where {@param token} is being used + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise + */ + @StageType + public int getSplitItemStage(@Nullable WindowContainerToken token) { + if (token == null) { + return STAGE_TYPE_UNDEFINED; + } + + if (mMainStage.containsToken(token)) { + return STAGE_TYPE_MAIN; + } else if (mSideStage.containsToken(token)) { + return STAGE_TYPE_SIDE; + } + + return STAGE_TYPE_UNDEFINED; + } + @Override public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { final StageTaskListener topLeftStage = @@ -2178,21 +2282,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 +2305,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,12 +2450,31 @@ 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 - // would keep out not empty. Prevent intercepting all transitions for split screen when - // it is in the background and not identify to handle it. - return (!out.isEmpty() || isSplitScreenVisible()) ? out : null; + if (!out.isEmpty()) { + // One of the cases above handled it + return out; + } else if (isSplitScreenVisible()) { + // If split is visible, only defer handling this transition if it's launching + // adjacent while there is already a split pair -- this may trigger PIP and + // that should be handled by the mixed handler. + final boolean deferTransition = requestHasLaunchAdjacentFlag(request) + && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0; + return !deferTransition ? out : null; + } + // Don't intercept the transition if we are not handling it as a part of one of the + // cases above and it is not already visible + return null; } else { if (isOpening && getStageOfTask(triggerTask) != null) { // One task is appearing into split, prepare to enter split screen. @@ -2405,7 +2510,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent( recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); } - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT); + @StageType int topStage = STAGE_TYPE_UNDEFINED; + if (isSplitScreenVisible()) { + // Get the stage where a child exists to keep that stage onTop + if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) { + topStage = STAGE_TYPE_MAIN; + } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) { + topStage = STAGE_TYPE_SIDE; + } + } + prepareExitSplitScreen(topStage, outWCT); } } @@ -2519,6 +2633,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 +2805,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; } @@ -2811,7 +2936,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } - /** Synchronize split-screen state with transition and make appropriate preparations. */ + /** + * Synchronize split-screen state with transition and make appropriate preparations. + * @param toStage The stage that will not be dismissed. If set to + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed + */ public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { @@ -3158,7 +3287,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 +3302,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/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index dc91a11dc64f..29be34347b22 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 @@ -385,8 +385,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 { 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..cefbb17d783d 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 @@ -202,15 +202,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(); } @@ -409,7 +404,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..f0bb665f8082 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 @@ -40,15 +40,17 @@ 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.DesktopModeController; +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.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; @@ -70,6 +72,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private RecentsTransitionHandler mRecentsHandler; private StageCoordinator mSplitHandler; private final KeyguardTransitionHandler mKeyguardHandler; + private DesktopModeController mDesktopModeController; + private DesktopTasksController mDesktopTasksController; private UnfoldTransitionHandler mUnfoldHandler; private static class MixedTransition { @@ -87,8 +91,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; @@ -107,6 +114,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, WindowContainerTransaction mFinishWCT = null; /** + * Whether the transition has request for remote transition while mLeftoversHandler + * isn't remote transition handler. + * If true and the mLeftoversHandler can handle the transition, need to notify remote + * transition handler to consume the transition. + */ + boolean mHasRequestToRemote; + + /** * Mixed transitions are made up of multiple "parts". This keeps track of how many * parts are currently animating. */ @@ -117,14 +132,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 +147,20 @@ 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<DesktopModeController> desktopModeControllerOptional, + 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 +170,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mRecentsHandler != null) { mRecentsHandler.addMixer(this); } + mDesktopModeController = desktopModeControllerOptional.orElse(null); + mDesktopTasksController = desktopTasksControllerOptional.orElse(null); mUnfoldHandler = unfoldHandler.orElse(null); }, this); } @@ -200,6 +213,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition); mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); + if (mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { + mixed.mHasRequestToRemote = true; + mPlayer.getRemoteTransitionHandler().handleRequest(transition, request); + } return handler.second; } else if (mSplitHandler.isSplitScreenVisible() && isOpeningType(request.getType()) @@ -239,7 +256,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @Override public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) { - if (mRecentsHandler != null && mSplitHandler.isSplitScreenVisible()) { + if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible() + || DesktopModeStatus.isActive(mPlayer.getContext()))) { return this; } return null; @@ -254,6 +272,13 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); mixed.mLeftoversHandler = mRecentsHandler; mActiveTransitions.add(mixed); + } else if (DesktopModeStatus.isActive(mPlayer.getContext())) { + 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"); @@ -303,12 +328,24 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // the time of handleRequest, but we need more information than is available at that time. if (KeyguardTransitionHandler.handles(info)) { if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, - "Converting mixed transition into a keyguard transition"); - onTransitionConsumed(transition, false, null); + final MixedTransition keyguardMixed = + new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition); + mActiveTransitions.add(keyguardMixed); + final boolean hasAnimateKeyguard = animateKeyguard(keyguardMixed, info, + startTransaction, finishTransaction, finishCallback); + if (hasAnimateKeyguard) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Converting mixed transition into a keyguard transition"); + // Consume the original mixed transition + onTransitionConsumed(transition, false, null); + return true; + } else { + // Keyguard handler cannot handle it, process through original mixed + mActiveTransitions.remove(keyguardMixed); + } + } else if (mPipHandler != null) { + mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); } - mixed = new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition); - mActiveTransitions.add(mixed); } if (mixed == null) return false; @@ -319,8 +356,17 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { return false; } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { - return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction, - finishTransaction, finishCallback); + final boolean handledToPip = animateOpenIntentWithRemoteAndPip(mixed, info, + startTransaction, finishTransaction, finishCallback); + // Consume the transition on remote handler if the leftover handler already handle this + // transition. And if it cannot, the transition will be handled by remote handler, so + // don't consume here. + // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip + if (handledToPip && mixed.mHasRequestToRemote + && mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { + mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null); + } + return handledToPip; } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); @@ -340,6 +386,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 +415,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 +487,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) { @@ -463,8 +512,26 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // make a new startTransaction because pip's startEnterAnimation "consumes" it so // we need a separate one to send over to launcher. SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; + if (mSplitHandler.isSplitScreenVisible()) { + // The non-going home case, we could be pip-ing one of the split stages and keep + // showing the other + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == pipChange) { + // Ignore the change/task that's going into Pip + continue; + } + @SplitScreen.StageType int splitItemStage = + mSplitHandler.getSplitItemStage(change.getLastParent()); + if (splitItemStage != STAGE_TYPE_UNDEFINED) { + topStageToKeep = splitItemStage; + break; + } + } + } // Let split update internal state for dismiss. - mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED, + mSplitHandler.prepareDismissAnimation(topStageToKeep, EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, finishTransaction); @@ -563,12 +630,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 +658,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 +670,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 +688,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 +708,49 @@ 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; + } + //Sync desktop mode state (proto 1) + if (mDesktopModeController != null) { + mDesktopModeController.syncSurfaceState(info, finishTransaction); + return true; + } + //Sync desktop mode state (proto 2) + 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 +821,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,8 +851,13 @@ 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); } + if (mixed.mHasRequestToRemote) { + mPlayer.getRemoteTransitionHandler().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..de03f5826925 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 @@ -99,7 +99,6 @@ import android.window.WindowContainerTransaction; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.policy.AttributeCache; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.common.ProtoLog; @@ -182,7 +181,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { /* broadcastPermission = */ null, mMainHandler); - AttributeCache.init(mContext); + TransitionAnimation.initAttributeCache(mContext, mMainHandler); } private void updateEnterpriseThumbnailDrawable() { @@ -300,7 +299,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 +308,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 +322,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 +406,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..bbf67a6155d7 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,7 +133,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } mMainExecutor.execute(() -> { mRequestedRemotes.remove(transition); - finishCallback.onTransitionFinished(wct, null /* wctCB */); + finishCallback.onTransitionFinished(wct); }); } }; @@ -153,8 +153,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { 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..ba0ef20c412e 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 @@ -19,13 +19,12 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; +import android.util.Slog; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import java.util.ArrayList; - /** * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these * as sentinels for fast-forwarding through animations when the screen is off. @@ -34,30 +33,25 @@ import java.util.ArrayList; * don't register it like a normal handler. */ class SleepHandler implements Transitions.TransitionHandler { - final ArrayList<IBinder> mSleepTransitions = new ArrayList<>(); - @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - mSleepTransitions.remove(transition); - startTransaction.apply(); - finishCallback.onTransitionFinished(null, null); - return true; + if (info.hasChangesOrSideEffects()) { + Slog.e(Transitions.TAG, "Real changes included in a SLEEP transition"); + return false; + } else { + startTransaction.apply(); + finishCallback.onTransitionFinished(null); + return true; + } } @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - mSleepTransitions.add(transition); return new WindowContainerTransaction(); } - - @Override - public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, - @Nullable SurfaceControl.Transaction finishTransaction) { - mSleepTransitions.remove(transition); - } } 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/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..ce8191067ae9 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 @@ -113,7 +113,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 +143,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 +224,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..3a0bdd668fbc 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,14 @@ 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 +36,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 +45,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,6 +57,7 @@ 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; @@ -67,7 +72,11 @@ import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; 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; @@ -85,6 +94,7 @@ 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; @@ -106,37 +116,41 @@ 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; 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) { + Optional<DesktopTasksController> desktopTasksController + ) { 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()); } @VisibleForTesting @@ -144,23 +158,25 @@ 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) { 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; @@ -169,6 +185,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory; mInputMonitorFactory = inputMonitorFactory; mTransactionFactory = transactionFactory; + mDesktopModeKeyguardChangeListener = desktopModeKeyguardChangeListener; + + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener); } @Override @@ -177,6 +200,24 @@ 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.isActive(mContext) + && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false)); + mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo)); + } + } + } + }); + } + + @Override public boolean onTaskOpening( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -193,7 +234,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 +270,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { removeTaskFromEventReceiver(oldTaskInfo.displayId); incrementEventReceiverTasks(taskInfo.displayId); } - decoration.relayout(taskInfo); } @@ -236,7 +280,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 +318,17 @@ 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, 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 +338,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTaskToken = taskInfo.token; mDragPositioningCallback = dragPositioningCallback; mDragDetector = new DragDetector(this); + mGestureDetector = new GestureDetector(mContext, this); } @Override @@ -301,14 +347,14 @@ 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() + if (mSplitScreenController != null + && mSplitScreenController.isSplitScreenVisible()) { + int remainingTaskPosition = mTaskId == mSplitScreenController .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT; - ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get() + ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController .getTaskInfo(remainingTaskPosition); - mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId); + mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId); } } else if (id == R.id.back_button) { mTaskOperations.injectBackKey(); @@ -321,12 +367,24 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } 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); + } 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) { @@ -336,6 +394,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId)); decoration.closeHandleMenu(); } + } else if (id == R.id.maximize_window) { + final RunningTaskInfo taskInfo = decoration.mTaskInfo; + mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize( + taskInfo, decoration)); + decoration.closeHandleMenu(); } } @@ -347,7 +410,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return false; } moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId)); - return mDragDetector.onMotionEvent(e); + return mDragDetector.onMotionEvent(v, e); } private void moveTaskToFront(RunningTaskInfo taskInfo) { @@ -362,7 +425,7 @@ 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() && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { @@ -373,6 +436,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { == WINDOWING_MODE_FULLSCREEN) { return false; } + if (mGestureDetector.onTouchEvent(e)) { + return true; + } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mDragPointerId = e.getPointerId(0); @@ -380,21 +446,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 +487,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 @@ -540,9 +635,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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; + // 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 +650,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } case MotionEvent.ACTION_UP: { if (relevantDecor == null) { - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; mTransitionDragActive = false; return; } @@ -567,14 +664,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } 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 +691,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int statusBarHeight = getStatusBarHeight( relevantDecor.mTaskInfo.displayId); if (ev.getY() > statusBarHeight) { - if (!mDragToDesktopAnimationStarted) { - mDragToDesktopAnimationStarted = true; + if (mMoveToDesktopAnimator == null) { + 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 +711,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { case MotionEvent.ACTION_CANCEL: { mTransitionDragActive = false; - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; } } } @@ -683,24 +778,9 @@ 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); } else { @@ -711,9 +791,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 +845,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; + } + if (mDesktopModeKeyguardChangeListener.isKeyguardVisibleAndOccluded() + && taskInfo.isFocused) { return false; } return DesktopModeStatus.isProto2Enabled() + && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD + && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop() && mDisplayController.getDisplayContext(taskInfo.displayId) .getResources().getConfiguration().smallestScreenWidthDp >= 600; } @@ -796,9 +882,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mMainChoreographer, mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); + windowDecoration.createResizeVeil(); final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback( - windowDecoration, taskInfo); + windowDecoration); final DesktopModeTouchEventListener touchEventListener = new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback); @@ -811,20 +898,17 @@ 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); } } @@ -857,6 +941,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(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..a359395711e3 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,6 @@ 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.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -39,6 +38,7 @@ import android.view.View; import android.view.ViewConfiguration; 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.ShellTaskOrganizer; @@ -178,15 +178,12 @@ 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.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 @@ -235,8 +232,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()) @@ -294,23 +294,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(); } @@ -479,6 +493,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } } + @Override + int getCaptionHeightId() { + return R.dimen.freeform_decor_caption_height; + } + /** * Add transition to mTransitionsPausingRelayout */ 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..e32bd42acf74 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,23 @@ public class DragPositioningCallbackUtility { } /** + * 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..e0ee25242550 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,10 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { } mTaskOrganizer.applyTransaction(wct); } else if (mCtrlType == CTRL_TYPE_UNDEFINED - && !mDisallowedAreaForEndBounds.contains((int) x, (int) y)) { + && y > mDisallowedAreaForEndBoundsHeight) { 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 +137,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/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..bfce72bcadf0 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 @@ -102,11 +102,17 @@ public class ResizeVeil { } /** - * 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 +121,36 @@ 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) { + final ValueAnimator animator = new ValueAnimator(); + animator.setFloatValues(0f, 1f); + animator.setDuration(RESIZE_ALPHA_DURATION); + animator.addUpdateListener(animation -> { + t.setAlpha(mVeilSurface, animator.getAnimatedFraction()); + t.apply(); + }); + + t.show(mVeilSurface) + .addTransactionCommittedListener( + mContext.getMainExecutor(), () -> animator.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 +167,16 @@ public class ResizeVeil { */ public void updateResizeVeil(Rect newBounds) { SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); + updateResizeVeil(t, newBounds); + } + + /** + * Calls relayout to update task and veil bounds. + * + * @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) { 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..c9c58de6e82a 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,9 @@ 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 (y > mDisallowedAreaForEndBoundsHeight) { + DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds, + mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y); DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(), mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer); } @@ -148,6 +152,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 +168,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..0b0d9d5086f4 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 @@ -64,6 +64,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; @@ -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); } @@ -264,6 +287,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setColor(mTaskSurface, mTmpColor) .show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) + .setShadowRadius(mTaskSurface, shadowRadius) .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { startT.setCornerRadius(mTaskSurface, params.mCornerRadius); @@ -301,6 +325,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 +373,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 +443,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; 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..a9eb8829bd2c 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 @@ -23,13 +23,14 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( 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 +38,7 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( openMenuButton.setOnClickListener(onCaptionButtonClickListener) openMenuButton.setOnTouchListener(onCaptionTouchListener) closeWindowButton.setOnClickListener(onCaptionButtonClickListener) + maximizeWindowButton.setOnClickListener(onCaptionButtonClickListener) closeWindowButton.setOnTouchListener(onCaptionTouchListener) appNameTextView.text = appName appIconImageView.setImageDrawable(appIcon) @@ -45,10 +47,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 +76,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..b062fbd510ca 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -23,14 +23,67 @@ 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: "WMShellFlickerTestsSplitScreen-src", srcs: [ - "src/**/*.java", - "src/**/*.kt", + "src/com/android/wm/shell/flicker/splitscreen/*.kt", + "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt", + ], +} + +filegroup { + name: "WMShellFlickerServiceTests-src", + srcs: [ + "src/com/android/wm/shell/flicker/service/**/*.kt", ], - manifest: "AndroidManifest.xml", - test_config: "AndroidTest.xml", +} + +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 +92,85 @@ 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", + "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", + ":WMShellFlickerTestsSplitScreen-src", + ":WMShellFlickerServiceTests-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", + ], +} + +android_test { + name: "WMShellFlickerTestsSplitScreen", + 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", + ":WMShellFlickerTestsSplitScreen-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", ], } 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..c8a96377800f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml @@ -0,0 +1,101 @@ +<?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> + <!-- 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"/> + <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.server.wm.flicker.pip/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.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..6a87de47def4 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"/> @@ -57,10 +58,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..5a8155a66d30 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.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.pip"> + + <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/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..36acb58d6139 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,29 @@ 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 +49,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) + Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest) } - @After - fun after() { - resetLetterboxEducationEnabled() - } + fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation) - 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") - } - - fun IFlickerTestData.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 +74,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 +82,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..5a1136f97c6f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt @@ -0,0 +1,108 @@ +/* + * 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 + } +}
\ No newline at end of file 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..67d5718e6c1f 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 @@ -70,9 +72,7 @@ class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) @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 +90,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..e6ca261a317f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt @@ -0,0 +1,128 @@ +/* + * 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..d3f3c5b7c672 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt @@ -0,0 +1,275 @@ +/* + * 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) +open 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..ea0392cee95a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt @@ -0,0 +1,63 @@ +/* + * 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..5c7d1d8df2e8 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,9 +23,9 @@ 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 @@ -35,7 +35,7 @@ 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) @@ -72,28 +72,31 @@ abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) { 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..bc565bc5fd42 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 @@ -21,7 +21,7 @@ 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.LegacyFlickerTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObject2 @@ -44,7 +44,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FlakyTest(bugId = 217777115) -open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +open 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/ChangeActiveActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt index bdfdad59c600..abc6b9f9a746 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt @@ -17,11 +17,11 @@ package com.android.wm.shell.flicker.bubble import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class ChangeActiveActivityFromBubbleTestCfArm(flicker: FlickerTest) : +open class ChangeActiveActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) : ChangeActiveActivityFromBubbleTest(flicker) 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..3f28ae848d1f 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,9 +19,10 @@ 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.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 @@ -44,7 +45,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +open class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) { private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager private val displaySize = DisplayMetrics() @@ -73,4 +74,14 @@ 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) + ) + } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt index 62fa7b4516c7..ee55eca31072 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt @@ -17,11 +17,11 @@ package com.android.wm.shell.flicker.bubble import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class DragToDismissBubbleScreenTestCfArm(flicker: FlickerTest) : +class DragToDismissBubbleScreenTestCfArm(flicker: LegacyFlickerTest) : DragToDismissBubbleScreenTest(flicker) 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..26aca1830889 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 @@ -21,7 +21,7 @@ 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.RequiresDevice @@ -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..508539411aa0 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,7 +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 android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until @@ -42,7 +42,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class OpenActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +open 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/OpenActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt index 6c61710d6284..6a46d23ad2a1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt @@ -17,10 +17,11 @@ package com.android.wm.shell.flicker.bubble import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class OpenActivityFromBubbleTestCfArm(flicker: FlickerTest) : OpenActivityFromBubbleTest(flicker) +class OpenActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) : + OpenActivityFromBubbleTest(flicker) 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..a926bb7d85c3 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,7 +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 android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until @@ -41,7 +41,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +open class SendBubbleNotificationTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -55,6 +55,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/bubble/SendBubbleNotificationTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt index e323ebf3b5c8..a401cb494822 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt @@ -17,11 +17,11 @@ package com.android.wm.shell.flicker.bubble import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class SendBubbleNotificationTestCfArm(flicker: FlickerTest) : +open class SendBubbleNotificationTestCfArm(flicker: LegacyFlickerTest) : SendBubbleNotificationTest(flicker) 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..2f7a25ea586d 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,7 @@ 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 org.junit.Assume import org.junit.FixMethodOrder @@ -53,10 +53,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 { 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..68bc9a28967e 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,7 +20,7 @@ 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.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.Test @@ -53,7 +53,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ClosePipBySwipingDownTest(flicker: FlickerTest) : ClosePipTransition(flicker) { +open 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 index 02f60100d069..7a668897fbbe 100644 --- 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 @@ -18,8 +18,8 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,20 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ClosePipBySwipingDownTestCfArm(flicker: FlickerTest) : ClosePipBySwipingDownTest(flicker) { +class ClosePipBySwipingDownTestCfArm(flicker: LegacyFlickerTest) : + ClosePipBySwipingDownTest(flicker) { 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/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt index e52b71e602f9..a17144b7cef3 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/ClosePipTransition.kt @@ -20,14 +20,14 @@ 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/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt index 86fe583c94e6..dc48696f3197 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,7 +19,7 @@ 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 org.junit.FixMethodOrder import org.junit.Test @@ -53,7 +53,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ClosePipWithDismissButtonTest(flicker: FlickerTest) : ClosePipTransition(flicker) { +open 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 index 05262feceba5..718b14babc4f 100644 --- 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 @@ -18,8 +18,8 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,21 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ClosePipWithDismissButtonTestCfArm(flicker: FlickerTest) : +open class ClosePipWithDismissButtonTestCfArm(flicker: LegacyFlickerTest) : ClosePipWithDismissButtonTest(flicker) { 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/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index 01d67cc35a14..5e392628aa6a 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,7 +19,7 @@ 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 org.junit.Assume import org.junit.FixMethodOrder @@ -44,10 +44,8 @@ import org.junit.runners.Parameterized @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() } - } +open 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 index 90f99c0c4cae..2b3e76a964c4 100644 --- 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 @@ -17,7 +17,7 @@ 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.LegacyFlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -27,4 +27,5 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterPipOnUserLeaveHintTestCfArm(flicker: FlickerTest) : EnterPipOnUserLeaveHintTest(flicker) +class EnterPipOnUserLeaveHintTestCfArm(flicker: LegacyFlickerTest) : + 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..ec35837bc8dd 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 @@ -21,11 +21,12 @@ 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 com.android.server.wm.flicker.entireScreenCovered @@ -68,15 +69,13 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flicker) { +open 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 +94,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 +205,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 index 58416660826f..92642197e9be 100644 --- 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 @@ -17,9 +17,10 @@ package com.android.wm.shell.flicker.pip 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.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.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -29,19 +30,19 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterPipToOtherOrientationCfArm(flicker: FlickerTest) : +open class EnterPipToOtherOrientationCfArm(flicker: LegacyFlickerTest) : EnterPipToOtherOrientation(flicker) { 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(): 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/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt index cdbdb85a9195..6d20740e239c 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/EnterPipTransition.kt @@ -20,16 +20,14 @@ 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 */ @@ -126,15 +124,14 @@ abstract class EnterPipTransition(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/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt index 95725b64a48a..76c811cbbeea 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,7 +18,7 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.runner.RunWith @@ -50,7 +50,7 @@ import org.junit.runners.Parameterized @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 index 4390f0bb70b2..78e80497747c 100644 --- 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 @@ -18,8 +18,8 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,20 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterPipViaAppUiButtonTestCfArm(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) { +class EnterPipViaAppUiButtonTestCfArm(flicker: LegacyFlickerTest) : + EnterPipViaAppUiButtonTest(flicker) { 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/ExitPipToAppTransition.kt index 5ac9829b6c8f..dfffba831dc3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt @@ -19,14 +19,14 @@ package com.android.wm.shell.flicker.pip 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/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt index 0b3d16a8087d..b80b7483ba4d 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,7 +18,7 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.runner.RunWith @@ -52,7 +52,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) { +open 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 index eccb85d98798..e25c0d6eddc0 100644 --- 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 @@ -18,8 +18,8 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,21 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ExitPipToAppViaExpandButtonTestCfArm(flicker: FlickerTest) : +class ExitPipToAppViaExpandButtonTestCfArm(flicker: LegacyFlickerTest) : ExitPipToAppViaExpandButtonTest(flicker) { 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/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt index bb2d40becdc9..f003ed8a77e0 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,7 +18,7 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.runner.RunWith @@ -51,7 +51,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) { +open 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 index 6ab6a1f0bb73..be19f3cd1970 100644 --- 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 @@ -18,8 +18,8 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,20 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ExitPipToAppViaIntentTestCfArm(flicker: FlickerTest) : ExitPipToAppViaIntentTest(flicker) { +class ExitPipToAppViaIntentTestCfArm(flicker: LegacyFlickerTest) : + ExitPipToAppViaIntentTest(flicker) { 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/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index fd16b6ea6ada..a1d3a117482e 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,8 +21,8 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.Test @@ -55,7 +55,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) { +open class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.doubleClickPipWindow(wmHelper) } } @@ -142,15 +142,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 index c09623490041..3095cac94598 100644 --- 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 @@ -18,8 +18,8 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,21 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ExpandPipOnDoubleClickTestTestCfArm(flicker: FlickerTest) : +class ExpandPipOnDoubleClickTestTestCfArm(flicker: LegacyFlickerTest) : ExpandPipOnDoubleClickTest(flicker) { 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/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt index 253aa4cae5c7..8c8d280aea9a 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,8 +20,8 @@ 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 org.junit.FixMethodOrder import org.junit.Test @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) { +open class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) } } @@ -55,15 +55,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 index e064bf2ee921..1a1ce6823f3b 100644 --- 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 @@ -18,8 +18,8 @@ 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 android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,20 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ExpandPipOnPinchOpenTestCfArm(flicker: FlickerTest) : ExpandPipOnPinchOpenTest(flicker) { +class ExpandPipOnPinchOpenTestCfArm(flicker: LegacyFlickerTest) : + ExpandPipOnPinchOpenTest(flicker) { 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/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt index 094060f86691..421ad757f76a 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,9 @@ 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.utils.Direction import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -55,7 +55,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..dffc822e7aec 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,11 +18,12 @@ 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 @@ -38,7 +39,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) { +open class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val imeApp = ImeAppHelper(instrumentation) override val thisTransition: FlickerBuilder.() -> Unit = { @@ -80,7 +81,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 index d3d77d20662e..63292a4f2ca3 100644 --- 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 @@ -17,9 +17,10 @@ package com.android.wm.shell.flicker.pip 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.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.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,7 +29,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) : +class MovePipOnImeVisibilityChangeTestCfArm(flicker: LegacyFlickerTest) : MovePipOnImeVisibilityChangeTest(flicker) { companion object { private const val TAG_IME_VISIBLE = "imeIsVisible" @@ -36,7 +37,7 @@ class MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) : @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/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt index 109354ab5c79..a8fb63de244b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt @@ -19,15 +19,15 @@ package com.android.wm.shell.flicker.pip 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/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt index 27b061b67a85..992f1bc4ace3 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,9 @@ 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.utils.Direction import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -55,14 +55,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..0c6fc5636a5b 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,12 +16,12 @@ 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 org.junit.FixMethodOrder import org.junit.Test @@ -34,7 +34,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 +62,7 @@ class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) { } } - @Postsubmit + @Presubmit @Test fun pipLayerMovesAwayFromEdge() { flicker.assertLayers { @@ -81,13 +81,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..de64f78a31eb 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,12 +17,12 @@ package com.android.wm.shell.flicker.pip import android.graphics.Rect -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 android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.setRotation @@ -38,7 +38,7 @@ import org.junit.runners.Parameterized @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,7 +80,7 @@ class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) { /** * Checks that the visible region area of [pipApp] moves to closest edge during the animation. */ - @Postsubmit + @Presubmit @Test fun pipLayerMovesToClosestEdge() { flicker.assertLayers { @@ -99,15 +99,14 @@ class PipDragThenSnapTest(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/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index 60bf5ffdc7af..0295741bc441 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,13 +16,12 @@ 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 org.junit.FixMethodOrder import org.junit.Test @@ -35,14 +34,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 +55,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/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index 17a178f78de3..096af39488e9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -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,16 +78,16 @@ 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 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..c315e744bd55 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 @@ -21,10 +21,11 @@ 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.RequiresDevice import com.android.server.wm.flicker.testapp.ActivityOptions @@ -46,7 +47,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 +70,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 +150,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..0ff9cfff873e 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,10 +17,11 @@ 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 @@ -58,7 +59,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker) { +open 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 +155,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 index b7a2c47e3b32..25164711b2e5 100644 --- 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 @@ -16,9 +16,10 @@ package com.android.wm.shell.flicker.pip +import android.tools.common.flicker.assertions.FlickerTest import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -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.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -27,18 +28,18 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ShowPipAndRotateDisplayCfArm(flicker: FlickerTest) : ShowPipAndRotateDisplay(flicker) { +class ShowPipAndRotateDisplayCfArm(flicker: LegacyFlickerTest) : ShowPipAndRotateDisplay(flicker) { 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(): Collection<FlickerTest> { - return FlickerTestFactory.rotationTests() + return LegacyFlickerTestFactory.rotationTests() } } } 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/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt new file mode 100644 index 000000000000..610cedefe594 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.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.wm.shell.flicker.service + +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.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(UnlockScreenRule()) + .around( + NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ 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/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..566adec75615 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test + +@RequiresDevice +class CopyContentInSplitGesturalNavLandscapeBenchmark : 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/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..92b62273d8cb --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test + +@RequiresDevice +class CopyContentInSplitGesturalNavPortraitBenchmark : 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/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..e6d56b5c94d3 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark : + 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/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..6752c58bd568 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByDividerGesturalNavPortraitBenchmark : + 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/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..7c9ab9939dd0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark : + 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/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..4b795713cb23 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark : + 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/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..04950799732e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test + +@RequiresDevice +class DragDividerToResizeGesturalNavLandscapeBenchmark : 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/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..71ef48bea686 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test + +@RequiresDevice +class DragDividerToResizeGesturalNavPortraitBenchmark : 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/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..c78729c6dc92 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark : + 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/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..30bce2f657b1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark : + 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/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..b33ea7c89158 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.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. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark : + 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/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..07a86a57117b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.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. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark : + 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/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..9a1d12787b9d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark : + 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/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..266e268a3537 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark : + 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/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..83fc30bceb7b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark : + 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/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..b2f19299c7f0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark : + 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/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..dae92dddbfec --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark : + 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/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..732047ba38ad --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark : + 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/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..1de7efd7970a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test + +@RequiresDevice +class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark : + 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/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..1a046aa5b09e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test + +@RequiresDevice +class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark : + 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/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..6e88f0eddee8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark : + 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/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..d26a29c80583 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark : + 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/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..4a552b0aed6a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark : + 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/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..b7376eaea66d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark : + 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/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..b2d05e4a2632 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark : + 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/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..6de31b1315e4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark : + 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/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..aab18a6d27b9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test + +@RequiresDevice +class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark : + 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/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..b074f2c161c9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * 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.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test + +@RequiresDevice +class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark : + 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/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..c402aa4444d8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.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. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RequiresDevice +@RunWith(BlockJUnit4ClassRunner::class) +class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : 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/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..840401c23a91 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.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. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RequiresDevice +@RunWith(BlockJUnit4ClassRunner::class) +class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : 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/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..fa1be63296e0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.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 DismissSplitScreenByDividerGesturalNavPortrait : + DismissSplitScreenByDivider(Rotation.ROTATION_0) { + + @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/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..00f607343b3b --- /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(["QUICKSWITCH"]) + @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..b3340e77dbfa --- /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(["QUICKSWITCH"]) + @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..7cbc1c3c272c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.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.flicker + +import android.tools.common.flicker.annotation.ExpectedScenarios +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(["QUICKSWITCH"]) + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() +} 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..2eb81e0d0492 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.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.flicker + +import android.tools.common.flicker.annotation.ExpectedScenarios +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(["QUICKSWITCH"]) + @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..e530f6369609 --- /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.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..e9fc43746d27 --- /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.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..416692c37b34 --- /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.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..494a246d2f50 --- /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.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..369bdfc1103d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.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.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 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() { + 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.`package`, primaryApp.`package`) + 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..776c397cc354 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.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.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 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() { + 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..5d67dc7e231b --- /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.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.`package`, primaryApp.`package`) + 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..5bbb42fd1864 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.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.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 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() { + 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.`package`, primaryApp.`package`) + 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..c2100f641a55 --- /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.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..70f3bed9afdc --- /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.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..86f394da0231 --- /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.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..d7b611e04d9d --- /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.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..bf4c381b1c3a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.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.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) + + 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..4a9c32f10415 --- /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.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..383a6b39a2b6 --- /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.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..3702be9541a3 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 @@ -17,25 +17,20 @@ 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.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..8b906305506f 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 @@ -21,17 +21,17 @@ 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.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..50f6a382a702 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 @@ -20,15 +20,15 @@ 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.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..ca9c13009848 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 @@ -17,23 +17,18 @@ 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.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,19 +53,6 @@ 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) @@ -127,8 +109,6 @@ class DragDividerToResize(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/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt index e55868675da7..f8d1e1f1f498 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 @@ -22,19 +22,19 @@ 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.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..ff5d93550541 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 @@ -22,18 +22,18 @@ 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.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..7c710777087d 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 @@ -21,17 +21,17 @@ 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.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..83717062b05e 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 @@ -22,19 +22,19 @@ 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.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..0bfdbb4de7c5 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 @@ -20,17 +20,17 @@ 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.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/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt index 195b73a14a72..7ce995ac64aa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt @@ -18,11 +18,12 @@ package com.android.wm.shell.flicker.splitscreen 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) 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..88bbc0e7880b 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 @@ -21,15 +21,15 @@ 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.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..e85dc24a7781 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 @@ -21,15 +21,15 @@ 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.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..f7a9ed073002 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 @@ -21,15 +21,15 @@ 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.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..66f9b85ea572 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 @@ -17,27 +17,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.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.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/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt index 676c150815ad..851391d37323 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 @@ -21,15 +21,15 @@ import android.tools.common.NavBar import android.tools.common.flicker.subject.region.RegionSubject 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.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,21 +37,21 @@ 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) } @@ -65,33 +65,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 +104,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..e5c1e75a75f4 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,15 @@ 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 +33,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 +51,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..e4e1af9d24ce 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,14 @@ 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 +32,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 +57,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..b2dd02bf2c41 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,14 @@ 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 +32,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 +43,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..078859166dbc 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,16 @@ 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 +34,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 +42,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..884e4513e893 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,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.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 +35,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 @@ -57,38 +53,18 @@ open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: Flic } } - 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..e5c40b69726c 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,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.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 +35,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 +55,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 +63,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..04510014c437 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,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.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 +35,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) @@ -62,33 +59,13 @@ open class EnterSplitScreenByDragFromShortcutBenchmark(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 = 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..9e0ca1b20f09 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,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.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 +35,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() = { @@ -56,26 +52,6 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Flic } } - /** {@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 +60,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..06b4fe7e0eb4 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,14 @@ 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 +32,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 +52,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/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index 65fb1358a9b0..007b7518b16e 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,18 @@ 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 +36,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 +50,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 +123,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..10c8eebf7d1d 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,15 @@ 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 +33,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 +51,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..a6e750fed70e 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,15 @@ 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 +33,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 +49,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..7e8d5fb83157 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,15 @@ 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 +33,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 +49,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..56edad1ded19 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,14 @@ 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 +32,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 +61,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..065d4d62be42 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,11 @@ 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 +33,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 +47,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..e5c124cbe775 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 @@ -304,7 +304,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( } } -fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowBecomesVisible(component: IComponentMatcher) { assertWm { this.isAppWindowInvisible(component) .then() @@ -316,39 +316,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 +356,7 @@ fun FlickerTest.dockedStackDividerBecomesVisible() { } } -fun FlickerTest.dockedStackDividerBecomesInvisible() { +fun LegacyFlickerTest.dockedStackDividerBecomesInvisible() { assertLayers { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() @@ -364,11 +364,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 +380,7 @@ fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd( } } -fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( rotation: Rotation, primaryComponent: IComponentMatcher ) { @@ -392,7 +392,7 @@ fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( } } -fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd( rotation: Rotation, secondaryComponent: IComponentMatcher ) { @@ -404,7 +404,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..c2f184a03380 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", 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/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/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/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/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..fe2da5dd19fc 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 @@ -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; 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..f85d707d55f9 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,9 @@ 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.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,8 +70,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import dagger.Lazy; - /** * Tests for {@link CompatUIController}. * @@ -82,21 +84,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 +144,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) { @@ -504,13 +524,175 @@ public class CompatUIControllerTest extends ShellTestCase { .createLayout(anyBoolean()); } + @Test + public void testUpdateActiveTaskInfo_newTask_visibleAndFocused_updated() { + // Simulate user aspect ratio button being shown for previous task + mController.setHasShownUserAspectRatioSettingsButton(true); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + // Create new task + final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ true); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo); + + // Check topActivityTaskId is updated to the taskId of the new task and + // hasShownUserAspectRatioSettingsButton has been reset to false + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton()); + } + + @Test + public void testUpdateActiveTaskInfo_newTask_notVisibleOrFocused_notUpdated() { + // Create new task + final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ true); + + // Simulate task being shown + mController.updateActiveTaskInfo(taskInfo); + + // Check topActivityTaskId is updated to the taskId of the new task and + // hasShownUserAspectRatioSettingsButton has been reset to false + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton()); + + // Simulate user aspect ratio button being shown + mController.setHasShownUserAspectRatioSettingsButton(true); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + final int newTaskId = TASK_ID + 1; + + // Create visible but NOT focused task + final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ false); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo1); + + // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton + // remains true + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + // Create focused but NOT visible task + final TaskInfo taskInfo2 = createTaskInfo(DISPLAY_ID, newTaskId, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ false, + /* isFocused */ true); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo2); + + // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton + // remains true + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + // Create NOT focused but NOT visible task + final TaskInfo taskInfo3 = createTaskInfo(DISPLAY_ID, newTaskId, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ false, + /* isFocused */ false); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo3); + + // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton + // remains true + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + } + + @Test + public void testUpdateActiveTaskInfo_sameTask_notUpdated() { + // Create new task + final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ true); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo); + + // Check topActivityTaskId is updated to the taskId of the new task and + // hasShownUserAspectRatioSettingsButton has been reset to false + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton()); + + // Simulate user aspect ratio button being shown + mController.setHasShownUserAspectRatioSettingsButton(true); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + // Simulate same task being re-shown + mController.updateActiveTaskInfo(taskInfo); + + // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton + // remains true + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + } + + @Test + public void testUpdateActiveTaskInfo_transparentTask_notUpdated() { + // Create new task + final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ true); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo); + + // Check topActivityTaskId is updated to the taskId of the new task and + // hasShownUserAspectRatioSettingsButton has been reset to false + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton()); + + // Simulate user aspect ratio button being shown + mController.setHasShownUserAspectRatioSettingsButton(true); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + + final int newTaskId = TASK_ID + 1; + + // Create transparent task + final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true, + /* isFocused */ true, /* isTopActivityTransparent */ true); + + // Simulate new task being shown + mController.updateActiveTaskInfo(taskInfo1); + + // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton + // remains true + Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId()); + Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); + } + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, @CameraCompatControlState int cameraCompatControlState) { + return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState, + /* isVisible */ false, /* isFocused */ false, + /* isTopActivityTransparent */ false); + } + + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, + @CameraCompatControlState int cameraCompatControlState, boolean isVisible, + boolean isFocused) { + return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState, + isVisible, isFocused, /* isTopActivityTransparent */ false); + } + + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, + @CameraCompatControlState int cameraCompatControlState, boolean isVisible, + boolean isFocused, boolean isTopActivityTransparent) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; taskInfo.displayId = displayId; taskInfo.topActivityInSizeCompat = hasSizeCompat; taskInfo.cameraCompatControlState = cameraCompatControlState; + taskInfo.isVisible = isVisible; + taskInfo.isFocused = isFocused; + taskInfo.isTopActivityTransparent = isTopActivityTransparent; return taskInfo; } } 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..f460d1b09e34 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java @@ -0,0 +1,155 @@ +/* + * 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, + () -> false, s -> {}); + + 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..5a4d6c812c17 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -0,0 +1,390 @@ +/* + * 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.testing.TestableLooper.RunWithLooper; +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; +import java.util.function.Supplier; + +/** + * Tests for {@link UserAspectRatioSettingsWindowManager}. + * + * Build/Install/Run: + * atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest + */ +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +@SmallTest +public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { + + private static final int TASK_ID = 1; + + @Mock private SyncTransactionQueue mSyncTransactionQueue; + @Mock + private Supplier<Boolean> mUserAspectRatioButtonShownChecker; + @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, + mUserAspectRatioButtonShownChecker, s -> {}); + spyOn(mWindowManager); + doReturn(mLayout).when(mWindowManager).inflateLayout(); + doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); + doReturn(false).when(mUserAspectRatioButtonShownChecker).get(); + } + + @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 testLayoutHasUserAspectRatioSettingsButton() { + clearInvocations(mWindowManager); + spyOn(mWindowManager); + TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true); + + // User aspect ratio settings button has not yet been shown. + doReturn(false).when(mUserAspectRatioButtonShownChecker).get(); + + // Check the layout has the user aspect ratio settings button. + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + assertTrue(mWindowManager.mHasUserAspectRatioSettingsButton); + + // User aspect ratio settings button has been shown and is still visible. + spyOn(mWindowManager); + doReturn(true).when(mWindowManager).isShowingButton(); + doReturn(true).when(mUserAspectRatioButtonShownChecker).get(); + + // Check the layout still has the user aspect ratio settings button. + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + assertTrue(mWindowManager.mHasUserAspectRatioSettingsButton); + + // User aspect ratio settings button has been shown and has timed out so is no longer + // visible. + doReturn(false).when(mWindowManager).isShowingButton(); + doReturn(true).when(mUserAspectRatioButtonShownChecker).get(); + + // Check the layout no longer has the user aspect ratio button. + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + assertFalse(mWindowManager.mHasUserAspectRatioSettingsButton); + } + + @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 index d6387ee5ae13..605a762a395f 100644 --- 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 @@ -59,6 +59,7 @@ import android.window.WindowContainerTransaction.HierarchyOp; import androidx.test.filters.SmallTest; 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; 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..5d87cf8b25a6 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,25 @@ 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.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 +81,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,12 +90,17 @@ 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 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>() @@ -114,6 +124,7 @@ class DesktopTasksControllerTest : ShellTestCase() { return DesktopTasksController( context, shellInit, + shellCommandHandler, shellController, displayController, shellTaskOrganizer, @@ -122,8 +133,10 @@ class DesktopTasksControllerTest : ShellTestCase() { transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, + mToggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository, - TestShellExecutor() + launchAdjacentController, + shellExecutor ) } @@ -262,17 +275,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 +307,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 +329,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 +341,23 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToFullscreen() { + 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 +380,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 +498,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 +570,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 +628,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) @@ -590,6 +723,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..29a757c19d98 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 @@ -22,6 +22,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.view.Display.DEFAULT_DISPLAY +import com.android.wm.shell.MockToken import com.android.wm.shell.TestRunningTaskInfoBuilder class DesktopTestHelpers { 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..c40cd4069cab 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 @@ -37,7 +37,7 @@ 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; 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..82fe5f2f6afc 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,7 +48,7 @@ public class TvPipGravityTest extends ShellTestCase { private TvPipBoundsState mTvPipBoundsState; private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; - private PipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; @Before @@ -57,11 +58,11 @@ public class TvPipGravityTest extends ShellTestCase { } 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); } 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/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..568db919818c 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 @@ -61,9 +61,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 +73,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 +105,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 +123,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 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/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index ff380e92322d..b32e0d6b4b39 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 @@ -176,7 +176,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 +299,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 +449,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 +524,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 +565,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 +599,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 +822,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 +834,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. @@ -1153,7 +1152,7 @@ public class ShellTransitionTests extends ShellTestCase { } @Test - public void testEmptyTransitionStillReportsKeyguardGoingAway() { + public void testEmptyTransition_withKeyguardGoingAway_plays() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); @@ -1172,6 +1171,65 @@ public class ShellTransitionTests extends ShellTestCase { } @Test + public void testSleepTransition_withKeyguardGoingAway_plays(){ + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a no-op transition + TransitionInfo info = new TransitionInfoBuilder( + TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If keyguard-going-away flag set, then it shouldn't be aborted. + assertEquals(1, mDefaultHandler.activeCount()); + } + + @Test + public void testSleepTransition_withChanges_plays(){ + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a transition with some changes + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP) + .addChange(TRANSIT_OPEN).build(); + info.setTrack(0); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If there is an actual change, then it shouldn't be aborted. + assertEquals(1, mDefaultHandler.activeCount()); + } + + + @Test + public void testSleepTransition_empty_SyncBySleepHandler() { + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a no-op transition + TransitionInfo info = new TransitionInfoBuilder( + TRANSIT_SLEEP, 0x0, true /* noOp */).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If there is nothing to actually play, it should not be offered to handlers. + assertEquals(0, mDefaultHandler.activeCount()); + } + + @Test public void testMultipleTracks() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); @@ -1449,13 +1507,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 +1536,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 +1544,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..596d6dd3a3d2 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; @@ -53,7 +56,8 @@ 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,7 +86,6 @@ 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; @@ -92,6 +95,11 @@ 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; private final List<InputManager> mMockInputManagers = new ArrayList<>(); private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel; @@ -105,16 +113,18 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { mContext, mMainHandler, mMainChoreographer, + mShellInit, mTaskOrganizer, mDisplayController, + mShellController, mSyncQueue, mTransitions, Optional.of(mDesktopModeController), Optional.of(mDesktopTasksController), - Optional.of(mSplitScreenController), mDesktopModeWindowDecorFactory, mMockInputMonitorFactory, - mTransactionFactory + mTransactionFactory, + mDesktopModeKeyguardChangeListener ); doReturn(mDesktopModeWindowDecoration) @@ -123,12 +133,16 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { 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 @@ -254,6 +268,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()); + } + private void runOnMainThread(Runnable r) throws Exception { final Handler mainHandler = new Handler(Looper.getMainLooper()); final CountDownLatch latch = new CountDownLatch(1); 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/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt index 69604ddf0af1..6f0599aa8243 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) @@ -101,6 +92,15 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { } 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..3465ddd9d101 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) @@ -119,6 +108,17 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } 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..7fc1c99bb44e 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 @@ -57,7 +57,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; @@ -316,7 +315,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 +410,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,8 +515,7 @@ public class WindowDecorationTests extends ShellTestCase { private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { - return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(), - mMockDisplayController, mMockShellTaskOrganizer, + return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, taskInfo, testSurface, new MockObjectSupplier<>(mMockSurfaceControlBuilders, () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))), 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..c87c15669a09 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -717,6 +717,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/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp index 613f52b32bea..651d526511ef 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, 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..74955503dbb1 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -19,8 +19,10 @@ #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> @@ -94,25 +96,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 +122,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 +138,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 +147,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 +157,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 +172,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 +193,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 +219,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/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 16b35ffcabac..f5b3ca602469 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -193,6 +193,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(); @@ -405,8 +406,17 @@ 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 (wasSkipped(mCurrentFrameInfo)) { + // Use the oldest skipped frame in case we skip more than a single frame + if (!mSkippedFrameInfo) { + mSkippedFrameInfo.emplace(); + mSkippedFrameInfo->vsyncId = + mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId); + mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime); + } + } else { mCurrentFrameInfo = mJankTracker.startFrame(); + mSkippedFrameInfo.reset(); } mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo); @@ -602,10 +612,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); } } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 5219b5757008..32ac5af94c14 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -366,6 +366,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/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/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/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 7bcd45c6b643..9aa2e1db4461 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; } } |